mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
displays tweaks
This commit is contained in:
@@ -496,5 +496,49 @@
|
||||
</changes>
|
||||
<url>https://github.com/bancika/diy-layout-creator/releases</url>
|
||||
</org.diylc.appframework.update.Version>
|
||||
<org.diylc.appframework.update.Version>
|
||||
<versionNumber>
|
||||
<major>6</major>
|
||||
<minor>7</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2026-09-30 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Character LCD - 16x2 and 20x4, with I2C backpack or 16-pin parallel interface</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>0.96" OLED Display (SSD1306) - I2C and SPI</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>7-Segment Display - 1-digit 0.56 inch, 4-digit in 0.36 inch and 0.56 inch, and TM1637 driver module</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>8x8 LED Matrix (MAX7219)</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>TFT Touch Screen (ILI9341) - 2.8 inch 240x320 with touch and SD card slot</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>NeoPixel Ring (WS2812B) - 12, 16 and 24 LEDs</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>NeoPixel Stick (WS2812B) - 8 LEDs</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Bill of Materials now lists the selected variant of microcontroller boards and displays, so different versions of the same board are no longer merged into a single row</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</changes>
|
||||
<url>https://github.com/bancika/diy-layout-creator/releases</url>
|
||||
</org.diylc.appframework.update.Version>
|
||||
</a>
|
||||
</java.util.Arrays_-ArrayList>
|
||||
|
||||
@@ -90,6 +90,11 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
|
||||
public static Color BUTTON_ACTUATOR_COLOR = Color.decode("#A0A0A0");
|
||||
public static Color DARK_PLASTIC_COLOR = Color.decode("#181818");
|
||||
public static Color DARK_PLASTIC_BORDER = Color.decode("#333333");
|
||||
public static Color RGB_LED_BODY_COLOR = Color.decode("#FDFEFE");
|
||||
public static Color RGB_LED_BODY_BORDER = Color.decode("#D0D3D4");
|
||||
public static Color RGB_LED_LENS_COLOR = Color.decode("#EAECEE");
|
||||
public static Color RGB_LED_LENS_BORDER = Color.decode("#BDC3C7");
|
||||
public static Color RGB_LED_CHIP_COLOR = Color.decode("#333333");
|
||||
public static Color SILK_COLOR = Color.WHITE;
|
||||
|
||||
public static Size PIN_SIZE = new Size(0.04d, SizeUnit.in);
|
||||
@@ -223,6 +228,26 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
|
||||
@Override
|
||||
public void setValue(Void value) {}
|
||||
|
||||
/**
|
||||
* The variant this board is set to, or {@code null} for a board that is sold in only one form.
|
||||
* Boards name their variant property differently -- {@code Version}, {@code Size}, {@code Type} --
|
||||
* so each one that has one answers here for itself.
|
||||
*/
|
||||
protected String getVariantLabel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* These boards have no value in the usual sense, so the BOM's value column carries the variant
|
||||
* instead. It is not cosmetic: {@code BomMaker} groups rows on type name plus value, so without
|
||||
* it every variant of a board collapses into a single row and a BOM cannot say which one to buy.
|
||||
*/
|
||||
@Override
|
||||
public String getValueForDisplay() {
|
||||
String variant = getVariantLabel();
|
||||
return variant == null ? "" : variant;
|
||||
}
|
||||
|
||||
protected abstract void updateControlPoints();
|
||||
|
||||
protected void invalidateCache() {}
|
||||
|
||||
@@ -239,4 +239,33 @@ public class MakerBoardPainter {
|
||||
g2d.setColor(AbstractMakerBoard.BUTTON_ACTUATOR_COLOR);
|
||||
g2d.fill(new Ellipse2D.Double(x + (w - actuatorD) / 2.0, y + (h - actuatorD) / 2.0, actuatorD, actuatorD));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to draw an addressable RGB LED in a 5050 package -- the white body, the milky phosphor
|
||||
* lens over it and the driver die visible through the lens. Shared by every WS2812B part, so
|
||||
* strips, rings and sticks all render the same LED.
|
||||
*
|
||||
* @param g2d Graphics2D context
|
||||
* @param cx centre X coordinate of the package
|
||||
* @param cy centre Y coordinate of the package
|
||||
* @param size edge length of the square package; lens and die scale with it
|
||||
*/
|
||||
public static void drawAddressableLed(Graphics2D g2d, double cx, double cy, double size) {
|
||||
double half = size / 2.0;
|
||||
g2d.setColor(AbstractMakerBoard.RGB_LED_BODY_COLOR);
|
||||
g2d.fill(new RoundRectangle2D.Double(cx - half, cy - half, size, size, 2, 2));
|
||||
g2d.setColor(AbstractMakerBoard.RGB_LED_BODY_BORDER);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(0.5f));
|
||||
g2d.draw(new RoundRectangle2D.Double(cx - half, cy - half, size, size, 2, 2));
|
||||
|
||||
double lensR = half * 0.7;
|
||||
g2d.setColor(AbstractMakerBoard.RGB_LED_LENS_COLOR);
|
||||
g2d.fill(new Ellipse2D.Double(cx - lensR, cy - lensR, lensR * 2, lensR * 2));
|
||||
g2d.setColor(AbstractMakerBoard.RGB_LED_LENS_BORDER);
|
||||
g2d.draw(new Ellipse2D.Double(cx - lensR, cy - lensR, lensR * 2, lensR * 2));
|
||||
|
||||
double dieSize = Math.max(2.0, lensR * 0.4);
|
||||
g2d.setColor(AbstractMakerBoard.RGB_LED_CHIP_COLOR);
|
||||
g2d.fill(new Rectangle2D.Double(cx - dieSize / 2.0, cy - dieSize / 2.0, dieSize, dieSize));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
// @ComponentDescriptor(name = "Character LCD (16x2 / 20x4)", category = "Displays & Outputs",
|
||||
// author = "Branislav Stojkovic", description = "HD44780-Compatible Character LCD Display (Parallel / I2C Backpack)",
|
||||
// instanceNamePrefix = "LCD", zOrder = IDIYComponent.BOARD,
|
||||
// bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
// enableCache = true)
|
||||
@ComponentDescriptor(name = "Character LCD (16x2 / 20x4)", category = "Displays & Outputs",
|
||||
author = "Branislav Stojkovic", description = "HD44780-Compatible Character LCD Display (Parallel / I2C Backpack)",
|
||||
instanceNamePrefix = "LCD", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class CharacterLCD extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -93,9 +93,11 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
@Override public String toString() { return label; }
|
||||
}
|
||||
|
||||
// clearance from the top edge to the pin row
|
||||
public static Size HEADER_OFFSET = new Size(2.54d, SizeUnit.mm);
|
||||
|
||||
public static Color PCB_GREEN = Color.decode("#1B5E20");
|
||||
public static Color SCREEN_BG = Color.decode("#1E88E5");
|
||||
public static Color SCREEN_TEXT = Color.decode("#FFFFFF");
|
||||
public static Color BEZEL_COLOR = Color.decode("#212121");
|
||||
|
||||
public static final String[] PIN_NAMES_I2C = new String[] {"GND", "VCC", "SDA", "SCL"};
|
||||
@@ -137,6 +139,17 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
// Both properties decide which part you buy, so the BOM carries them together.
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
LCDSize lcdSize = getLcdSize();
|
||||
if (lcdSize == null) {
|
||||
return null;
|
||||
}
|
||||
LCDInterface lcdInterface = getLcdInterface();
|
||||
return lcdInterface == null ? lcdSize.toString() : lcdSize + ", " + lcdInterface;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Backlight Color")
|
||||
public Color getScreenColor() {
|
||||
return screenColor;
|
||||
@@ -157,15 +170,36 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
return "Pin " + (index + 1);
|
||||
}
|
||||
|
||||
private int getPinCount() {
|
||||
return lcdInterface == LCDInterface.I2C_Backpack ? PIN_NAMES_I2C.length
|
||||
: PIN_NAMES_PARALLEL.length;
|
||||
}
|
||||
|
||||
private double getBoardWidth() {
|
||||
return new Size(lcdSize.getWidthMm(), SizeUnit.mm).convertToPixels();
|
||||
}
|
||||
|
||||
private double getBoardLength() {
|
||||
return new Size(lcdSize.getHeightMm(), SizeUnit.mm).convertToPixels();
|
||||
}
|
||||
|
||||
/** Left edge of the board, derived so that the pin row sits centred on the top edge. */
|
||||
private double getBoardX(double x) {
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
return x - (getBoardWidth() - (getPinCount() - 1) * spacing) / 2.0;
|
||||
}
|
||||
|
||||
private double getBoardY(double y) {
|
||||
return y - HEADER_OFFSET.convertToPixels();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControlPoints() {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
|
||||
int pinCount = (lcdInterface == LCDInterface.I2C_Backpack) ? 4 : 16;
|
||||
double[][] relativeOffsets = new double[pinCount][2];
|
||||
|
||||
for (int i = 0; i < pinCount; i++) {
|
||||
double[][] relativeOffsets = new double[getPinCount()][2];
|
||||
for (int i = 0; i < relativeOffsets.length; i++) {
|
||||
relativeOffsets[i][0] = i * spacing;
|
||||
relativeOffsets[i][1] = 0;
|
||||
}
|
||||
@@ -176,13 +210,8 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
@Override
|
||||
public Shape getBodyShape() {
|
||||
Point2D p0 = controlPoints[0];
|
||||
double x = p0.getX();
|
||||
double y = p0.getY();
|
||||
double boardW = new Size(lcdSize.getWidthMm(), SizeUnit.mm).convertToPixels();
|
||||
double boardH = new Size(lcdSize.getHeightMm(), SizeUnit.mm).convertToPixels();
|
||||
double boardX = x - 60;
|
||||
double boardY = y - 20;
|
||||
return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 10, 10);
|
||||
return new RoundRectangle2D.Double(getBoardX(p0.getX()), getBoardY(p0.getY()), getBoardWidth(),
|
||||
getBoardLength(), 10, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,11 +230,10 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
g2d.rotate(orientation.toRadians(), x, y);
|
||||
}
|
||||
|
||||
double boardW = new Size(lcdSize.getWidthMm(), SizeUnit.mm).convertToPixels();
|
||||
double boardH = new Size(lcdSize.getHeightMm(), SizeUnit.mm).convertToPixels();
|
||||
|
||||
double boardX = x - 60;
|
||||
double boardY = y - 20;
|
||||
double boardW = getBoardWidth();
|
||||
double boardH = getBoardLength();
|
||||
double boardX = getBoardX(x);
|
||||
double boardY = getBoardY(y);
|
||||
|
||||
Shape boardShape = getBodyShape();
|
||||
|
||||
@@ -246,14 +274,6 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
|
||||
g2d.setColor(screenColor);
|
||||
g2d.fill(new Rectangle2D.Double(screenX, screenY, screenW, screenH));
|
||||
|
||||
// Render character boxes
|
||||
g2d.setColor(SCREEN_TEXT);
|
||||
g2d.setFont(new Font("Monospaced", Font.BOLD, (lcdSize == LCDSize._16x2) ? 14 : 11));
|
||||
String line1 = (lcdSize == LCDSize._16x2) ? "HELLO WORLD! 16x2" : "DIYLC LCD MODULE";
|
||||
String line2 = (lcdSize == LCDSize._16x2) ? "DIY LAYOUT CREATOR" : "20x4 CHARACTER LCD";
|
||||
StringUtils.drawCenteredText(g2d, line1, screenX + screenW / 2.0, screenY + screenH / 3.0, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
StringUtils.drawCenteredText(g2d, line2, screenX + screenW / 2.0, screenY + screenH * 2.0 / 3.0, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
}
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
|
||||
@@ -23,7 +23,6 @@ package org.diylc.components.displays;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
@@ -50,11 +49,11 @@ import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
// @ComponentDescriptor(name = "8x8 LED Matrix (MAX7219)", category = "Displays & Outputs",
|
||||
// author = "Branislav Stojkovic", description = "MAX7219 Dot LED Matrix Display Module (Cascadable SPI)",
|
||||
// instanceNamePrefix = "DISP", zOrder = IDIYComponent.COMPONENT,
|
||||
// bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
// enableCache = true)
|
||||
@ComponentDescriptor(name = "8x8 LED Matrix (MAX7219)", category = "Displays & Outputs",
|
||||
author = "Branislav Stojkovic", description = "MAX7219 Dot LED Matrix Display Module (Cascadable SPI)",
|
||||
instanceNamePrefix = "DISP", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class LEDMatrix8x8 extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -66,6 +65,16 @@ public class LEDMatrix8x8 extends AbstractMakerBoard {
|
||||
|
||||
public static Size BOARD_WIDTH = new Size(32.0d, SizeUnit.mm);
|
||||
public static Size BOARD_HEIGHT = new Size(50.0d, SizeUnit.mm);
|
||||
// the matrix block is very nearly as wide as the board it sits on
|
||||
public static Size MATRIX_SIZE = new Size(30.0d, SizeUnit.mm);
|
||||
public static Size MATRIX_TOP_MARGIN = new Size(3.8d, SizeUnit.mm);
|
||||
// clearance from the bottom edge to the input pin row; the output row sits a board apart from it
|
||||
public static Size HEADER_OFFSET = new Size(2.54d, SizeUnit.mm);
|
||||
public static Size HEADER_SPACING = new Size(44.0d, SizeUnit.mm);
|
||||
public static Size CHIP_MARGIN_X = new Size(2.54d, SizeUnit.mm);
|
||||
public static Size CHIP_LENGTH = new Size(5.7d, SizeUnit.mm);
|
||||
public static Size CHIP_GAP = new Size(1.9d, SizeUnit.mm);
|
||||
public static Size SILK_OFFSET = new Size(2.0d, SizeUnit.mm);
|
||||
|
||||
public static final String[] PIN_NAMES = new String[] {
|
||||
// Input Header (0..4)
|
||||
@@ -105,16 +114,13 @@ public class LEDMatrix8x8 extends AbstractMakerBoard {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
|
||||
double[][] relativeOffsets = new double[10][2];
|
||||
double[][] relativeOffsets = new double[PIN_NAMES.length][2];
|
||||
|
||||
// Input header (0..4) at bottom
|
||||
// the input row sits near the bottom edge and the output row the same distance from the top
|
||||
double topY = -HEADER_SPACING.convertToPixels();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
relativeOffsets[i][0] = i * spacing;
|
||||
relativeOffsets[i][1] = 0;
|
||||
}
|
||||
// Output header (5..9) at top
|
||||
double topY = -new Size(44.0d, SizeUnit.mm).convertToPixels();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
relativeOffsets[5 + i][0] = i * spacing;
|
||||
relativeOffsets[5 + i][1] = topY;
|
||||
}
|
||||
@@ -122,16 +128,21 @@ public class LEDMatrix8x8 extends AbstractMakerBoard {
|
||||
rotatePoints(firstPoint, relativeOffsets);
|
||||
}
|
||||
|
||||
/** Left edge of the board, derived so that the five-pin rows sit centred across it. */
|
||||
private double getBoardX(double x) {
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
return x - (BOARD_WIDTH.convertToPixels() - 4 * spacing) / 2.0;
|
||||
}
|
||||
|
||||
private double getBoardY(double y) {
|
||||
return y - BOARD_HEIGHT.convertToPixels() + HEADER_OFFSET.convertToPixels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getBodyShape() {
|
||||
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 - (boardW - 4 * PIN_SPACING.convertToPixels()) / 2.0;
|
||||
double boardY = y - boardH + 20;
|
||||
return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 8, 8);
|
||||
return new RoundRectangle2D.Double(getBoardX(p0.getX()), getBoardY(p0.getY()),
|
||||
BOARD_WIDTH.convertToPixels(), BOARD_HEIGHT.convertToPixels(), 8, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,8 +163,8 @@ public class LEDMatrix8x8 extends AbstractMakerBoard {
|
||||
|
||||
double boardW = BOARD_WIDTH.convertToPixels();
|
||||
double boardH = BOARD_HEIGHT.convertToPixels();
|
||||
double boardX = x - (boardW - 4 * PIN_SPACING.convertToPixels()) / 2.0;
|
||||
double boardY = y - boardH + 20;
|
||||
double boardX = getBoardX(x);
|
||||
double boardY = getBoardY(y);
|
||||
|
||||
Shape boardShape = getBodyShape();
|
||||
|
||||
@@ -169,10 +180,9 @@ public class LEDMatrix8x8 extends AbstractMakerBoard {
|
||||
g2d.draw(boardShape);
|
||||
|
||||
if (!outlineMode) {
|
||||
// 8x8 LED Matrix block (square 32mm)
|
||||
double matrixSize = boardW - 10;
|
||||
double matrixX = boardX + 5;
|
||||
double matrixY = boardY + 30;
|
||||
double matrixSize = MATRIX_SIZE.convertToPixels();
|
||||
double matrixX = boardX + (boardW - matrixSize) / 2.0;
|
||||
double matrixY = boardY + MATRIX_TOP_MARGIN.convertToPixels();
|
||||
|
||||
g2d.setColor(MATRIX_BODY);
|
||||
g2d.fill(new RoundRectangle2D.Double(matrixX, matrixY, matrixSize, matrixSize, 6, 6));
|
||||
@@ -196,14 +206,21 @@ public class LEDMatrix8x8 extends AbstractMakerBoard {
|
||||
}
|
||||
}
|
||||
|
||||
// MAX7219 IC
|
||||
MakerBoardPainter.drawChip(g2d, boardX + 20, matrixY + matrixSize + 15, boardW - 40, 45, "MAX7219");
|
||||
double chipMarginX = CHIP_MARGIN_X.convertToPixels();
|
||||
MakerBoardPainter.drawChip(g2d, boardX + chipMarginX,
|
||||
matrixY + matrixSize + CHIP_GAP.convertToPixels(), boardW - 2 * chipMarginX,
|
||||
CHIP_LENGTH.convertToPixels(), "MAX7219");
|
||||
|
||||
// Silkscreen
|
||||
// each label sits just inside the board from the row it names
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.setFont(SILK_FONT_SMALL);
|
||||
StringUtils.drawCenteredText(g2d, "IN", x + 2 * PIN_SPACING.convertToPixels(), y - 16, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
StringUtils.drawCenteredText(g2d, "OUT", x + 2 * PIN_SPACING.convertToPixels(), y - boardH + 34, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
double silkOffset = SILK_OFFSET.convertToPixels();
|
||||
double silkX = x + 2 * PIN_SPACING.convertToPixels();
|
||||
StringUtils.drawCenteredText(g2d, "IN", silkX, y - silkOffset, HorizontalAlignment.CENTER,
|
||||
VerticalAlignment.CENTER);
|
||||
StringUtils.drawCenteredText(g2d, "OUT", silkX,
|
||||
y - HEADER_SPACING.convertToPixels() + silkOffset, HorizontalAlignment.CENTER,
|
||||
VerticalAlignment.CENTER);
|
||||
}
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
|
||||
@@ -50,11 +50,11 @@ import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
// @ComponentDescriptor(name = "0.96\" OLED Display (SSD1306)", category = "Displays & Outputs",
|
||||
// author = "Branislav Stojkovic", description = "0.96\" Monochrome 128x64 OLED Display Module (I2C / SPI)",
|
||||
// instanceNamePrefix = "DISP", zOrder = IDIYComponent.COMPONENT,
|
||||
// bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
// enableCache = true)
|
||||
@ComponentDescriptor(name = "0.96\" OLED Display (SSD1306)", category = "Displays & Outputs",
|
||||
author = "Branislav Stojkovic", description = "0.96\" Monochrome 128x64 OLED Display Module (I2C / SPI)",
|
||||
instanceNamePrefix = "DISP", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class OLEDDisplay extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -70,10 +70,25 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
|
||||
public static Color OLED_BLUE = Color.decode("#004488");
|
||||
public static Color GLASS_COLOR = Color.decode("#0D1B2A");
|
||||
public static Color GLASS_BORDER_COLOR = Color.decode("#334E68");
|
||||
public static Color ACTIVE_AREA_COLOR = Color.decode("#060D15");
|
||||
public static Color PIXEL_BLUE = Color.decode("#00D4FF");
|
||||
public static Color PIXEL_YELLOW = Color.decode("#FFD700");
|
||||
|
||||
public static Size BOARD_SIZE = new Size(27.0d, SizeUnit.mm);
|
||||
// The panel is nearly as wide as the board but much shorter, which leaves bare PCB above it for
|
||||
// the header and below it for the lower mounting holes.
|
||||
public static Size GLASS_WIDTH = new Size(26.7d, SizeUnit.mm);
|
||||
public static Size GLASS_LENGTH = new Size(19.3d, SizeUnit.mm);
|
||||
// 128 x 64 pixels on a 0.17mm pitch. The lit area is much smaller than the panel around it and
|
||||
// roughly twice as wide as it is tall, which is what makes the part read as a display.
|
||||
public static Size ACTIVE_WIDTH = new Size(21.744d, SizeUnit.mm);
|
||||
public static Size ACTIVE_LENGTH = new Size(10.864d, SizeUnit.mm);
|
||||
// Centre-to-centre in both directions, which is how the hole pattern is specified and what a
|
||||
// builder drills to; the inset from the board edge falls out of it.
|
||||
public static Size MOUNTING_HOLE_SPACING = new Size(24.0d, SizeUnit.mm);
|
||||
public static Size MOUNTING_HOLE_SIZE = new Size(2.0d, SizeUnit.mm);
|
||||
// clearance from the top edge to the pin row
|
||||
public static Size HEADER_OFFSET = new Size(1.8d, SizeUnit.mm);
|
||||
|
||||
public static final String[] PIN_NAMES_I2C = new String[] {"GND", "VCC", "SCL", "SDA"};
|
||||
public static final String[] PIN_NAMES_SPI = new String[] {"GND", "VCC", "D0 (CLK)", "D1 (MOSI)", "RES", "DC", "CS"};
|
||||
@@ -97,6 +112,12 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
OLEDInterface oledInterface = getOledInterface();
|
||||
return oledInterface == null ? null : oledInterface.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
if (oledInterface == OLEDInterface.I2C_4Pin) {
|
||||
@@ -107,15 +128,27 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
return "Pin " + (index + 1);
|
||||
}
|
||||
|
||||
private int getPinCount() {
|
||||
return oledInterface == OLEDInterface.I2C_4Pin ? PIN_NAMES_I2C.length : PIN_NAMES_SPI.length;
|
||||
}
|
||||
|
||||
/** Left edge of the board, derived so that the pin row sits centred on the top edge. */
|
||||
private double getBoardX(double x) {
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
return x - (BOARD_SIZE.convertToPixels() - (getPinCount() - 1) * spacing) / 2.0;
|
||||
}
|
||||
|
||||
private double getBoardY(double y) {
|
||||
return y - HEADER_OFFSET.convertToPixels();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControlPoints() {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
|
||||
int count = (oledInterface == OLEDInterface.I2C_4Pin) ? 4 : 7;
|
||||
double[][] relativeOffsets = new double[count][2];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
double[][] relativeOffsets = new double[getPinCount()][2];
|
||||
for (int i = 0; i < relativeOffsets.length; i++) {
|
||||
relativeOffsets[i][0] = i * spacing;
|
||||
relativeOffsets[i][1] = 0;
|
||||
}
|
||||
@@ -126,13 +159,9 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
@Override
|
||||
public Shape getBodyShape() {
|
||||
Point2D p0 = controlPoints[0];
|
||||
double x = p0.getX();
|
||||
double y = p0.getY();
|
||||
double boardSizePx = BOARD_SIZE.convertToPixels();
|
||||
int count = (oledInterface == OLEDInterface.I2C_4Pin) ? 4 : 7;
|
||||
double boardX = x - (boardSizePx - (count - 1) * PIN_SPACING.convertToPixels()) / 2.0;
|
||||
double boardY = y - 14;
|
||||
return new RoundRectangle2D.Double(boardX, boardY, boardSizePx, boardSizePx, 8, 8);
|
||||
return new RoundRectangle2D.Double(getBoardX(p0.getX()), getBoardY(p0.getY()), boardSizePx,
|
||||
boardSizePx, 8, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,9 +181,8 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
}
|
||||
|
||||
double boardSizePx = BOARD_SIZE.convertToPixels();
|
||||
int count = (oledInterface == OLEDInterface.I2C_4Pin) ? 4 : 7;
|
||||
double boardX = x - (boardSizePx - (count - 1) * PIN_SPACING.convertToPixels()) / 2.0;
|
||||
double boardY = y - 14;
|
||||
double boardX = getBoardX(x);
|
||||
double boardY = getBoardY(y);
|
||||
|
||||
Shape boardShape = getBodyShape();
|
||||
|
||||
@@ -170,31 +198,34 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
g2d.draw(boardShape);
|
||||
|
||||
if (!outlineMode) {
|
||||
// 4 Corner mounting holes
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + 16, boardY + 16, 16);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + 16, boardY + boardSizePx - 16, 16);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardSizePx - 16, boardY + 16, 16);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardSizePx - 16, boardY + boardSizePx - 16, 16);
|
||||
double holeInset = (boardSizePx - MOUNTING_HOLE_SPACING.convertToPixels()) / 2.0;
|
||||
double holeSize = MOUNTING_HOLE_SIZE.convertToPixels();
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset, boardY + holeInset, holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardSizePx - holeInset, boardY + holeInset,
|
||||
holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset, boardY + boardSizePx - holeInset,
|
||||
holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardSizePx - holeInset,
|
||||
boardY + boardSizePx - holeInset, holeSize);
|
||||
|
||||
// Glass OLED Panel
|
||||
double glassMarginX = 18;
|
||||
double glassW = boardSizePx - 2 * glassMarginX;
|
||||
double glassH = boardSizePx - 65;
|
||||
double glassX = boardX + glassMarginX;
|
||||
double glassY = boardY + 45;
|
||||
// The two rows of holes are inset equally, so centring the panel in the board is what centres
|
||||
// it between them and keeps it clear of all four.
|
||||
double glassW = GLASS_WIDTH.convertToPixels();
|
||||
double glassH = GLASS_LENGTH.convertToPixels();
|
||||
double glassX = boardX + (boardSizePx - glassW) / 2.0;
|
||||
double glassY = boardY + (boardSizePx - glassH) / 2.0;
|
||||
|
||||
g2d.setColor(GLASS_COLOR);
|
||||
g2d.fill(new RoundRectangle2D.Double(glassX, glassY, glassW, glassH, 4, 4));
|
||||
g2d.setColor(Color.decode("#334E68"));
|
||||
g2d.setColor(GLASS_BORDER_COLOR);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
g2d.draw(new RoundRectangle2D.Double(glassX, glassY, glassW, glassH, 4, 4));
|
||||
|
||||
// Display demo graphics (yellow top banner + blue body)
|
||||
g2d.setColor(PIXEL_YELLOW);
|
||||
g2d.setFont(SILK_FONT_SMALL);
|
||||
StringUtils.drawCenteredText(g2d, "SSD1306 128x64", glassX + glassW / 2.0, glassY + 15, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
|
||||
g2d.setColor(PIXEL_BLUE);
|
||||
StringUtils.drawCenteredText(g2d, "OLED DISPLAY", glassX + glassW / 2.0, glassY + glassH / 2.0 + 8, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
double activeW = ACTIVE_WIDTH.convertToPixels();
|
||||
double activeH = ACTIVE_LENGTH.convertToPixels();
|
||||
g2d.setColor(ACTIVE_AREA_COLOR);
|
||||
g2d.fill(new RoundRectangle2D.Double(glassX + (glassW - activeW) / 2.0,
|
||||
glassY + (glassH - activeH) / 2.0, activeW, activeH, 2, 2));
|
||||
}
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
|
||||
+201
-182
@@ -51,23 +51,70 @@ import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
// @ComponentDescriptor(name = "7-Segment Display", category = "Displays & Outputs",
|
||||
// author = "Branislav Stojkovic", description = "7-Segment LED Display (1-Digit, 4-Digit, or TM1637 I2C Driver Module)",
|
||||
// instanceNamePrefix = "DISP", zOrder = IDIYComponent.COMPONENT,
|
||||
// bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
// enableCache = true)
|
||||
@ComponentDescriptor(name = "7-Segment Display", category = "Displays & Outputs",
|
||||
author = "Branislav Stojkovic", description = "7-Segment LED Display (1-Digit, 4-Digit, or TM1637 I2C Driver Module)",
|
||||
instanceNamePrefix = "DISP", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* The packages this component can draw, each carrying its own dimensions in millimetres.
|
||||
*
|
||||
* <p>Provenance differs between them and is worth keeping straight. The digit heights are
|
||||
* definitional, since 0.36 and 0.56 inches are what the parts are named for. The 0.56 inch body,
|
||||
* digit width and pitch are the figures this class has always carried and have never been checked
|
||||
* against a datasheet. The 0.36 inch equivalents are **placeholders**, scaled from the digit
|
||||
* height, and are not measurements at all. See the plan's section 11.10; none of this is settled
|
||||
* until someone reads a datasheet or measures a part.
|
||||
*/
|
||||
public enum DisplayType {
|
||||
SingleDigit_10Pin("1-Digit 0.56\" (10-Pin DIP)"),
|
||||
FourDigit_12Pin("4-Digit Bare (12-Pin DIP)"),
|
||||
TM1637_Module_4Pin("4-Digit TM1637 Module (4-Pin)");
|
||||
SingleDigit_10Pin("1-Digit 0.56\" (10-Pin DIP)", 12.6d, 19.0d, 8.1d, 14.2d, 0d, 15.24d, true),
|
||||
FourDigit_0_36_12Pin("4-Digit 0.36\" Bare (12-Pin DIP)", 30.0d, 14.0d, 5.2d, 9.14d, 7.5d,
|
||||
10.16d, true),
|
||||
FourDigit_0_56_12Pin("4-Digit 0.56\" Bare (12-Pin DIP)", 50.3d, 19.0d, 8.1d, 14.2d, 12.7d,
|
||||
15.24d, true),
|
||||
TM1637_Module_4Pin("4-Digit TM1637 Module (4-Pin)", 42.0d, 24.0d, 5.5d, 9.2d, 7.62d, 0d, false);
|
||||
|
||||
private final String label;
|
||||
DisplayType(String label) { this.label = label; }
|
||||
private final double bodyWidthMm;
|
||||
private final double bodyLengthMm;
|
||||
private final double digitWidthMm;
|
||||
private final double digitHeightMm;
|
||||
private final double digitPitchMm;
|
||||
private final double rowSpacingMm;
|
||||
private final boolean dualRow;
|
||||
|
||||
DisplayType(String label, double bodyWidthMm, double bodyLengthMm, double digitWidthMm,
|
||||
double digitHeightMm, double digitPitchMm, double rowSpacingMm, boolean dualRow) {
|
||||
this.label = label;
|
||||
this.bodyWidthMm = bodyWidthMm;
|
||||
this.bodyLengthMm = bodyLengthMm;
|
||||
this.digitWidthMm = digitWidthMm;
|
||||
this.digitHeightMm = digitHeightMm;
|
||||
this.digitPitchMm = digitPitchMm;
|
||||
this.rowSpacingMm = rowSpacingMm;
|
||||
this.dualRow = dualRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Distance between the two pin rows. It cannot be one shared constant: a package only works if
|
||||
* its rows sit within its body, and the 0.36 inch part is shorter than the 0.6 inch spacing the
|
||||
* larger packages use.
|
||||
*/
|
||||
public double getRowSpacingMm() { return rowSpacingMm; }
|
||||
|
||||
@Override public String toString() { return label; }
|
||||
public double getBodyWidthMm() { return bodyWidthMm; }
|
||||
public double getBodyLengthMm() { return bodyLengthMm; }
|
||||
public double getDigitWidthMm() { return digitWidthMm; }
|
||||
public double getDigitHeightMm() { return digitHeightMm; }
|
||||
public double getDigitPitchMm() { return digitPitchMm; }
|
||||
|
||||
/** True for the DIP packages, which carry their pins in two rows rather than one. */
|
||||
public boolean isDualRow() { return dualRow; }
|
||||
}
|
||||
|
||||
public static Color BODY_BLACK = Color.decode("#1C1C1C");
|
||||
@@ -77,13 +124,19 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
public static Color LED_OFF = Color.decode("#2E2E2E");
|
||||
public static Color LED_OFF_BORDER = Color.decode("#222222");
|
||||
|
||||
// Exact physical proportions from datasheet
|
||||
public static Size DIGIT_HEIGHT = new Size(14.2d, SizeUnit.mm); // 0.56"
|
||||
public static Size DIGIT_WIDTH = new Size(8.1d, SizeUnit.mm);
|
||||
public static Size DIGIT_PITCH = new Size(12.7d, SizeUnit.mm);
|
||||
// Per-package dimensions live on DisplayType; these apply to every package.
|
||||
public static Size SEGMENT_THICKNESS = new Size(1.4d, SizeUnit.mm);
|
||||
public static Size SEGMENT_GAP = new Size(0.35d, SizeUnit.mm);
|
||||
public static double SLANT_DEGREES = 8.0d;
|
||||
// How far a module's header column sits in from the right edge. The DIP packages straddle their
|
||||
// pins with a row either side; a module carries its header down one edge instead.
|
||||
public static Size HEADER_EDGE_OFFSET = new Size(2.5d, SizeUnit.mm);
|
||||
/**
|
||||
* The digit is drawn at this fraction of the dimensions above. On the real part the digit window
|
||||
* runs almost to the pin rows -- at full size the two clear each other by around a hundredth of a
|
||||
* millimetre -- so a faithful drawing has the pins sitting on the digit. This is a deliberate
|
||||
* departure from the measurements for legibility, which is why it is a separate factor rather
|
||||
* than smaller digit sizes on {@link DisplayType}: those stay as the package measures.
|
||||
*/
|
||||
public static double DIGIT_DRAW_SCALE = 0.88d;
|
||||
|
||||
public static final String[] PIN_NAMES_1DIGIT = new String[] {
|
||||
"e", "d", "COM1", "c", "DP", "b", "a", "COM2", "f", "g"
|
||||
@@ -115,6 +168,12 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
DisplayType displayType = getDisplayType();
|
||||
return displayType == null ? null : displayType.toString();
|
||||
}
|
||||
|
||||
@EditableProperty(name = "LED Color")
|
||||
public Color getLedColor() {
|
||||
return ledColor;
|
||||
@@ -125,18 +184,27 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* True for the two bare four-digit packages. They differ in size but not in pin function, so they
|
||||
* share a pin array and differ only in what they measure.
|
||||
*/
|
||||
private boolean isFourDigitBare() {
|
||||
return displayType == DisplayType.FourDigit_0_36_12Pin
|
||||
|| displayType == DisplayType.FourDigit_0_56_12Pin;
|
||||
}
|
||||
|
||||
private String[] getPinNames() {
|
||||
if (displayType == DisplayType.SingleDigit_10Pin) {
|
||||
return PIN_NAMES_1DIGIT;
|
||||
}
|
||||
return isFourDigitBare() ? PIN_NAMES_4DIGIT : PIN_NAMES_TM1637;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
switch (displayType) {
|
||||
case SingleDigit_10Pin:
|
||||
if (index >= 0 && index < PIN_NAMES_1DIGIT.length) return PIN_NAMES_1DIGIT[index];
|
||||
break;
|
||||
case FourDigit_12Pin:
|
||||
if (index >= 0 && index < PIN_NAMES_4DIGIT.length) return PIN_NAMES_4DIGIT[index];
|
||||
break;
|
||||
case TM1637_Module_4Pin:
|
||||
if (index >= 0 && index < PIN_NAMES_TM1637.length) return PIN_NAMES_TM1637[index];
|
||||
break;
|
||||
String[] names = getPinNames();
|
||||
if (index >= 0 && index < names.length) {
|
||||
return names[index];
|
||||
}
|
||||
return "Pin " + (index + 1);
|
||||
}
|
||||
@@ -146,68 +214,49 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
|
||||
if (displayType == DisplayType.SingleDigit_10Pin) {
|
||||
double rowSpacing = new Size(0.6d, SizeUnit.in).convertToPixels(); // 120px
|
||||
double[][] relativeOffsets = new double[10][2];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int pinCount = getPinNames().length;
|
||||
double[][] relativeOffsets = new double[pinCount][2];
|
||||
|
||||
if (displayType.isDualRow()) {
|
||||
// DIP numbering: pin 1 at the bottom left, along the bottom row, then back along the top
|
||||
int perRow = pinCount / 2;
|
||||
double rowSpacing =
|
||||
new Size(displayType.getRowSpacingMm(), SizeUnit.mm).convertToPixels();
|
||||
for (int i = 0; i < perRow; i++) {
|
||||
relativeOffsets[i][0] = i * spacing;
|
||||
relativeOffsets[i][1] = 0;
|
||||
relativeOffsets[perRow + i][0] = (perRow - 1 - i) * spacing;
|
||||
relativeOffsets[perRow + i][1] = -rowSpacing;
|
||||
}
|
||||
for (int i = 0; i < 5; i++) {
|
||||
relativeOffsets[5 + i][0] = (4 - i) * spacing;
|
||||
relativeOffsets[5 + i][1] = -rowSpacing;
|
||||
}
|
||||
rotatePoints(firstPoint, relativeOffsets);
|
||||
} else if (displayType == DisplayType.FourDigit_12Pin) {
|
||||
double rowSpacing = new Size(0.6d, SizeUnit.in).convertToPixels();
|
||||
double[][] relativeOffsets = new double[12][2];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
relativeOffsets[i][0] = i * spacing;
|
||||
relativeOffsets[i][1] = 0;
|
||||
}
|
||||
for (int i = 0; i < 6; i++) {
|
||||
relativeOffsets[6 + i][0] = (5 - i) * spacing;
|
||||
relativeOffsets[6 + i][1] = -rowSpacing;
|
||||
}
|
||||
rotatePoints(firstPoint, relativeOffsets);
|
||||
} else {
|
||||
// TM1637 Module 4-pin
|
||||
double[][] relativeOffsets = new double[4][2];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
relativeOffsets[i][0] = i * spacing;
|
||||
relativeOffsets[i][1] = 0;
|
||||
// a module carries its header as a single column down one edge, not a row across the face
|
||||
for (int i = 0; i < pinCount; i++) {
|
||||
relativeOffsets[i][0] = 0;
|
||||
relativeOffsets[i][1] = i * spacing;
|
||||
}
|
||||
rotatePoints(firstPoint, relativeOffsets);
|
||||
}
|
||||
|
||||
rotatePoints(firstPoint, relativeOffsets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getBodyShape() {
|
||||
Point2D p0 = controlPoints[0];
|
||||
double x = p0.getX();
|
||||
double y = p0.getY();
|
||||
double boardW;
|
||||
double boardH;
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
double boardW = new Size(displayType.getBodyWidthMm(), SizeUnit.mm).convertToPixels();
|
||||
double boardH = new Size(displayType.getBodyLengthMm(), SizeUnit.mm).convertToPixels();
|
||||
int pinCount = getPinNames().length;
|
||||
double boardX;
|
||||
double boardY;
|
||||
|
||||
if (displayType == DisplayType.SingleDigit_10Pin) {
|
||||
boardW = new Size(12.6d, SizeUnit.mm).convertToPixels();
|
||||
boardH = new Size(19.0d, SizeUnit.mm).convertToPixels();
|
||||
double rowSpacing = new Size(0.6d, SizeUnit.in).convertToPixels();
|
||||
boardX = x - (boardW - 4 * PIN_SPACING.convertToPixels()) / 2.0;
|
||||
boardY = y - rowSpacing - (boardH - rowSpacing) / 2.0;
|
||||
} else if (displayType == DisplayType.FourDigit_12Pin) {
|
||||
boardW = new Size(50.3d, SizeUnit.mm).convertToPixels();
|
||||
boardH = new Size(19.0d, SizeUnit.mm).convertToPixels();
|
||||
double rowSpacing = new Size(0.6d, SizeUnit.in).convertToPixels();
|
||||
boardX = x - (boardW - 5 * PIN_SPACING.convertToPixels()) / 2.0;
|
||||
boardY = y - rowSpacing - (boardH - rowSpacing) / 2.0;
|
||||
if (displayType.isDualRow()) {
|
||||
double rowSpacing =
|
||||
new Size(displayType.getRowSpacingMm(), SizeUnit.mm).convertToPixels();
|
||||
boardX = p0.getX() - (boardW - (pinCount / 2 - 1) * spacing) / 2.0;
|
||||
boardY = p0.getY() - rowSpacing - (boardH - rowSpacing) / 2.0;
|
||||
} else {
|
||||
boardW = new Size(42.0d, SizeUnit.mm).convertToPixels();
|
||||
boardH = new Size(24.0d, SizeUnit.mm).convertToPixels();
|
||||
boardX = x - (boardW - 3 * PIN_SPACING.convertToPixels()) / 2.0;
|
||||
boardY = y - (boardH - PIN_SPACING.convertToPixels()) / 2.0;
|
||||
boardX = p0.getX() - boardW + HEADER_EDGE_OFFSET.convertToPixels();
|
||||
boardY = p0.getY() - (boardH - (pinCount - 1) * spacing) / 2.0;
|
||||
}
|
||||
|
||||
return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 6, 6);
|
||||
@@ -256,14 +305,16 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
g2d.draw(new RoundRectangle2D.Double(boardX + 3, boardY + 3, boardW - 6, boardH - 6, 3, 3));
|
||||
|
||||
// Single 0.56" 7-segment digit with exact datasheet dimensions
|
||||
double dw = DIGIT_WIDTH.convertToPixels();
|
||||
double dh = DIGIT_HEIGHT.convertToPixels();
|
||||
// one digit, drawn slightly under size so it keeps clear of the pins
|
||||
double dw = new Size(displayType.getDigitWidthMm(), SizeUnit.mm).convertToPixels()
|
||||
* DIGIT_DRAW_SCALE;
|
||||
double dh = new Size(displayType.getDigitHeightMm(), SizeUnit.mm).convertToPixels()
|
||||
* DIGIT_DRAW_SCALE;
|
||||
double dx = boardX + (boardW - dw) / 2.0 - 0.5 * SEGMENT_THICKNESS.convertToPixels();
|
||||
double dy = boardY + (boardH - dh) / 2.0;
|
||||
drawSevenSegmentDigit(g2d, dx, dy, dw, dh, "8.", ledColor);
|
||||
|
||||
} else if (displayType == DisplayType.FourDigit_12Pin) {
|
||||
} else if (isFourDigitBare()) {
|
||||
// Dark display face
|
||||
g2d.setColor(FACE_BLACK);
|
||||
g2d.fill(new RoundRectangle2D.Double(boardX + 3, boardY + 3, boardW - 6, boardH - 6, 3, 3));
|
||||
@@ -271,11 +322,16 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
g2d.draw(new RoundRectangle2D.Double(boardX + 3, boardY + 3, boardW - 6, boardH - 6, 3, 3));
|
||||
|
||||
// 4 digits across with 12.70mm center-to-center pitch
|
||||
double dw = DIGIT_WIDTH.convertToPixels();
|
||||
double dh = DIGIT_HEIGHT.convertToPixels();
|
||||
double pitch = DIGIT_PITCH.convertToPixels();
|
||||
double firstCenterX = boardX + new Size(6.1d, SizeUnit.mm).convertToPixels();
|
||||
// Four digits on the package's real centre-to-centre pitch, each drawn slightly under size
|
||||
// so they keep clear of the pins. The pitch is unscaled, so the digits stay on their true
|
||||
// centres and only the gaps between them widen, and the row is centred on the body rather
|
||||
// than measured from its left edge, so it stays put across packages of different widths.
|
||||
double dw = new Size(displayType.getDigitWidthMm(), SizeUnit.mm).convertToPixels()
|
||||
* DIGIT_DRAW_SCALE;
|
||||
double dh = new Size(displayType.getDigitHeightMm(), SizeUnit.mm).convertToPixels()
|
||||
* DIGIT_DRAW_SCALE;
|
||||
double pitch = new Size(displayType.getDigitPitchMm(), SizeUnit.mm).convertToPixels();
|
||||
double firstCenterX = boardX + boardW / 2.0 - 1.5 * pitch;
|
||||
double dy = boardY + (boardH - dh) / 2.0;
|
||||
|
||||
for (int d = 0; d < 4; d++) {
|
||||
@@ -284,16 +340,15 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
drawSevenSegmentDigit(g2d, dx, dy, dw, dh, "8.", ledColor);
|
||||
}
|
||||
|
||||
// Center colon dots between digits 1 & 2
|
||||
// Colon between the middle pair, sized off the glyph grid like the decimal point is, so it
|
||||
// tracks the digit instead of staying a fixed few pixels across.
|
||||
double colonX = boardX + boardW / 2.0;
|
||||
double colonY1 = dy + dh * 0.35;
|
||||
double colonY2 = dy + dh * 0.65;
|
||||
double dotD = dw / GLYPH_WIDTH * 2.0;
|
||||
g2d.setColor(ledColor);
|
||||
g2d.fill(new Ellipse2D.Double(colonX - 2.0, colonY1 - 2.0, 4.0, 4.0));
|
||||
g2d.fill(new Ellipse2D.Double(colonX - 2.0, colonY2 - 2.0, 4.0, 4.0));
|
||||
g2d.fill(new Ellipse2D.Double(colonX - dotD / 2.0, dy + dh * 0.35 - dotD / 2.0, dotD, dotD));
|
||||
g2d.fill(new Ellipse2D.Double(colonX - dotD / 2.0, dy + dh * 0.65 - dotD / 2.0, dotD, dotD));
|
||||
|
||||
} else {
|
||||
// TM1637 4-Digit Display Module
|
||||
} else if (displayType == DisplayType.TM1637_Module_4Pin) {
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + 14, boardY + 14, 12);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + 14, boardY + boardH - 14, 12);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - 14, boardY + 14, 12);
|
||||
@@ -302,7 +357,7 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
// Display Bezel (Center)
|
||||
double bezelW = new Size(30.0d, SizeUnit.mm).convertToPixels();
|
||||
double bezelH = new Size(14.0d, SizeUnit.mm).convertToPixels();
|
||||
double bezelX = boardX + (boardW - bezelW) / 2.0 - 6.0;
|
||||
double bezelX = boardX + (boardW - bezelW) / 2.0;
|
||||
double bezelY = boardY + (boardH - bezelH) / 2.0;
|
||||
|
||||
g2d.setColor(FACE_BLACK);
|
||||
@@ -324,13 +379,12 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
drawSevenSegmentDigit(g2d, dx, dy, dw, dh, "8.", ledColor);
|
||||
}
|
||||
|
||||
// Center colon dots
|
||||
// same colon treatment as the bare packages: sized and placed off the digit, not in pixels
|
||||
double colonX = bezelX + bezelW / 2.0;
|
||||
double colonY1 = bezelY + bezelH / 2.0 - 8.0;
|
||||
double colonY2 = bezelY + bezelH / 2.0 + 6.0;
|
||||
double dotD = dw / GLYPH_WIDTH * 2.0;
|
||||
g2d.setColor(ledColor);
|
||||
g2d.fill(new Ellipse2D.Double(colonX - 1.5, colonY1 - 1.5, 3.0, 3.0));
|
||||
g2d.fill(new Ellipse2D.Double(colonX - 1.5, colonY2 - 1.5, 3.0, 3.0));
|
||||
g2d.fill(new Ellipse2D.Double(colonX - dotD / 2.0, dy + dh * 0.35 - dotD / 2.0, dotD, dotD));
|
||||
g2d.fill(new Ellipse2D.Double(colonX - dotD / 2.0, dy + dh * 0.65 - dotD / 2.0, dotD, dotD));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,102 +429,58 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an authentic 7-segment digit matching exact engineering drawing specifications:
|
||||
* - 8.0° italic forward slant
|
||||
* - Realistic polygon segment geometry (trapezoids/hexagons with clean junction bevels)
|
||||
* - 1.40mm segment thickness, 0.35mm inter-segment gaps without overlap
|
||||
* - Unlit segments rendered in dim silhouette and active segments lit in onColor
|
||||
* - 1.40mm Decimal point circle at bottom-right
|
||||
* The seven segments as a unit-grid glyph: a 10 x 18 box with segments two units thick, mitred
|
||||
* at 45 degrees. Neighbours meet at a shared vertex rather than being held apart by a gap, and
|
||||
* the visible separation between them comes from outlining each segment in the background
|
||||
* colour. Index order is A through G, matching the SEG_* bits.
|
||||
*/
|
||||
private static final double[][][] SEGMENT_POINTS = new double[][][] {
|
||||
{{1, 1}, {2, 0}, {8, 0}, {9, 1}, {8, 2}, {2, 2}}, // A, top
|
||||
{{9, 1}, {10, 2}, {10, 8}, {9, 9}, {8, 8}, {8, 2}}, // B, upper right
|
||||
{{9, 9}, {10, 10}, {10, 16}, {9, 17}, {8, 16}, {8, 10}}, // C, lower right
|
||||
{{9, 17}, {8, 18}, {2, 18}, {1, 17}, {2, 16}, {8, 16}}, // D, bottom
|
||||
{{1, 17}, {0, 16}, {0, 10}, {1, 9}, {2, 10}, {2, 16}}, // E, lower left
|
||||
{{1, 9}, {0, 8}, {0, 2}, {1, 1}, {2, 2}, {2, 8}}, // F, upper left
|
||||
{{1, 9}, {2, 8}, {8, 8}, {9, 9}, {8, 10}, {2, 10}} // G, middle
|
||||
};
|
||||
|
||||
/** Width of the glyph grid above, in its own units. */
|
||||
private static final double GLYPH_WIDTH = 10.0d;
|
||||
|
||||
/** Height of the glyph grid above, in its own units. */
|
||||
private static final double GLYPH_HEIGHT = 18.0d;
|
||||
|
||||
/**
|
||||
* Draws one upright digit into the box at {@code x, y, w, h}, with the segment shapes taken from
|
||||
* {@link #SEGMENT_POINTS} scaled to that box. Unlit segments are drawn first as a dim
|
||||
* silhouette, then the lit ones, and finally every segment is outlined in the face colour so
|
||||
* that neighbours which share a vertex still read as separate segments.
|
||||
*/
|
||||
private void drawSevenSegmentDigit(Graphics2D g2d, double x, double y, double w, double h, String charToDisplay, Color onColor) {
|
||||
AffineTransform orig = g2d.getTransform();
|
||||
|
||||
// Exact 8.0° forward italic slant from engineering drawing
|
||||
g2d.translate(x + w / 2.0, y + h / 2.0);
|
||||
g2d.shear(-Math.tan(Math.toRadians(SLANT_DEGREES)), 0);
|
||||
g2d.translate(-w / 2.0, -h / 2.0);
|
||||
g2d.translate(x, y);
|
||||
|
||||
double t = Math.max(3.0, SEGMENT_THICKNESS.convertToPixels() * (h / DIGIT_HEIGHT.convertToPixels()));
|
||||
double g = Math.max(1.0, SEGMENT_GAP.convertToPixels() * (h / DIGIT_HEIGHT.convertToPixels()));
|
||||
double segW = w - t * 0.8;
|
||||
double sx = w / GLYPH_WIDTH;
|
||||
double sy = h / GLYPH_HEIGHT;
|
||||
|
||||
double x0 = 0;
|
||||
double x1 = segW;
|
||||
double y0 = 0;
|
||||
double ym = h / 2.0;
|
||||
double y1 = h;
|
||||
Shape[] segments = new Shape[SEGMENT_POINTS.length];
|
||||
for (int i = 0; i < SEGMENT_POINTS.length; i++) {
|
||||
double[][] points = SEGMENT_POINTS[i];
|
||||
Path2D.Double path = new Path2D.Double();
|
||||
path.moveTo(points[0][0] * sx, points[0][1] * sy);
|
||||
for (int p = 1; p < points.length; p++) {
|
||||
path.lineTo(points[p][0] * sx, points[p][1] * sy);
|
||||
}
|
||||
path.closePath();
|
||||
segments[i] = path;
|
||||
}
|
||||
|
||||
// Segment A (Top horizontal)
|
||||
Path2D pathA = new Path2D.Double();
|
||||
pathA.moveTo(x0 + t * 0.6 + g, y0);
|
||||
pathA.lineTo(x1 - t * 0.6 - g, y0);
|
||||
pathA.lineTo(x1 - g, y0 + t * 0.45);
|
||||
pathA.lineTo(x1 - t - g, y0 + t);
|
||||
pathA.lineTo(x0 + t + g, y0 + t);
|
||||
pathA.lineTo(x0 + g, y0 + t * 0.45);
|
||||
pathA.closePath();
|
||||
// the decimal point is not part of the glyph grid, so it is placed in the same units: one
|
||||
// segment thickness across, sitting on the baseline just clear of the digit
|
||||
double dpD = 2.0 * sx;
|
||||
Shape dpShape = new Ellipse2D.Double(GLYPH_WIDTH * sx + 0.5 * sx, 18.0 * sy - dpD, dpD, dpD);
|
||||
|
||||
// Segment B (Top-Right vertical)
|
||||
Path2D pathB = new Path2D.Double();
|
||||
pathB.moveTo(x1, y0 + t * 0.5 + g);
|
||||
pathB.lineTo(x1 - t * 0.35, y0 + g);
|
||||
pathB.lineTo(x1 - t, y0 + t + 1.4 * g);
|
||||
pathB.lineTo(x1 - t, ym - t * 0.5 - 0.8 * g);
|
||||
pathB.lineTo(x1, ym - 0.8 * g);
|
||||
pathB.closePath();
|
||||
|
||||
// Segment C (Bottom-Right vertical)
|
||||
Path2D pathC = new Path2D.Double();
|
||||
pathC.moveTo(x1, ym + 0.8 * g);
|
||||
pathC.lineTo(x1 - t, ym + t * 0.5 + 0.8 * g);
|
||||
pathC.lineTo(x1 - t, y1 - t - 1.4 * g);
|
||||
pathC.lineTo(x1 - t * 0.35, y1 - g);
|
||||
pathC.lineTo(x1, y1 - t * 0.5 - g);
|
||||
pathC.closePath();
|
||||
|
||||
// Segment D (Bottom horizontal)
|
||||
Path2D pathD = new Path2D.Double();
|
||||
pathD.moveTo(x0 + t + g, y1 - t);
|
||||
pathD.lineTo(x1 - t - g, y1 - t);
|
||||
pathD.lineTo(x1 - g, y1 - t * 0.45);
|
||||
pathD.lineTo(x1 - t * 0.6 - g, y1);
|
||||
pathD.lineTo(x0 + t * 0.6 + g, y1);
|
||||
pathD.lineTo(x0 + g, y1 - t * 0.45);
|
||||
pathD.closePath();
|
||||
|
||||
// Segment E (Bottom-Left vertical)
|
||||
Path2D pathE = new Path2D.Double();
|
||||
pathE.moveTo(x0, ym + 0.8 * g);
|
||||
pathE.lineTo(x0 + t, ym + t * 0.5 + 0.8 * g);
|
||||
pathE.lineTo(x0 + t, y1 - t - 1.4 * g);
|
||||
pathE.lineTo(x0 + t * 0.35, y1 - g);
|
||||
pathE.lineTo(x0, y1 - t * 0.5 - g);
|
||||
pathE.closePath();
|
||||
|
||||
// Segment F (Top-Left vertical)
|
||||
Path2D pathF = new Path2D.Double();
|
||||
pathF.moveTo(x0, y0 + t * 0.5 + g);
|
||||
pathF.lineTo(x0 + t * 0.35, y0 + g);
|
||||
pathF.lineTo(x0 + t, y0 + t + 1.4 * g);
|
||||
pathF.lineTo(x0 + t, ym - t * 0.5 - 0.8 * g);
|
||||
pathF.lineTo(x0, ym - 0.8 * g);
|
||||
pathF.closePath();
|
||||
|
||||
// Segment G (Middle horizontal hexagon)
|
||||
Path2D pathG = new Path2D.Double();
|
||||
pathG.moveTo(x0 + 1.4 * g, ym);
|
||||
pathG.lineTo(x0 + t * 0.6 + 1.4 * g, ym - t * 0.48);
|
||||
pathG.lineTo(x1 - t * 0.6 - 1.4 * g, ym - t * 0.48);
|
||||
pathG.lineTo(x1 - 1.4 * g, ym);
|
||||
pathG.lineTo(x1 - t * 0.6 - 1.4 * g, ym + t * 0.48);
|
||||
pathG.lineTo(x0 + t * 0.6 + 1.4 * g, ym + t * 0.48);
|
||||
pathG.closePath();
|
||||
|
||||
// Decimal Point (DP)
|
||||
double dpD = t * 0.95;
|
||||
Shape dpShape = new Ellipse2D.Double(x1 + t * 0.25, y1 - dpD, dpD, dpD);
|
||||
|
||||
Shape[] segments = new Shape[] { pathA, pathB, pathC, pathD, pathE, pathF, pathG };
|
||||
int mask = getSegmentMask(charToDisplay.isEmpty() ? '8' : charToDisplay.charAt(0));
|
||||
|
||||
// 1. Draw unlit segment silhouettes (dim background)
|
||||
@@ -495,6 +505,15 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
|
||||
g2d.fill(dpShape);
|
||||
}
|
||||
|
||||
// The source drawing outlines every segment in its own background colour, which is what stops
|
||||
// neighbours that share a vertex from fusing into one shape. The background here is the
|
||||
// display face, so the outline takes that colour rather than the white the drawing uses.
|
||||
g2d.setColor(FACE_BLACK);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke((float) (0.25 * (sx + sy) / 2.0)));
|
||||
for (Shape segment : segments) {
|
||||
g2d.draw(segment);
|
||||
}
|
||||
|
||||
g2d.setTransform(orig);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
/*
|
||||
*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
@@ -49,20 +49,38 @@ import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
// @ComponentDescriptor(name = "TFT Touch Screen (ILI9341)", category = "Displays & Outputs",
|
||||
// author = "Branislav Stojkovic", description = "2.8\" 240x320 Color TFT LCD Display with SPI Interface, Touch, and SD Slot",
|
||||
// instanceNamePrefix = "DISP", zOrder = IDIYComponent.BOARD,
|
||||
// bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
// enableCache = true)
|
||||
@ComponentDescriptor(name = "TFT Touch Screen (ILI9341)", category = "Displays & Outputs",
|
||||
author = "Branislav Stojkovic", description = "2.8\" 240x320 Color TFT LCD Display with SPI Interface, Touch, and SD Slot",
|
||||
instanceNamePrefix = "DISP", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class TFTDisplay extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Color TFT_RED = Color.decode("#C0392B");
|
||||
public static Color SCREEN_BG = Color.decode("#111111");
|
||||
public static Color GLASS_COLOR = Color.decode("#1A1A1A");
|
||||
|
||||
public static Size BOARD_WIDTH = new Size(86.0d, SizeUnit.mm);
|
||||
public static Size BOARD_HEIGHT = new Size(50.0d, SizeUnit.mm);
|
||||
// The board is taller than it is wide, with the header along the short top edge. Rotation is the
|
||||
// Orientation property's job, so the drawing keeps the orientation the module ships in.
|
||||
public static Size BOARD_WIDTH = new Size(50.0d, SizeUnit.mm);
|
||||
public static Size BOARD_HEIGHT = new Size(86.0d, SizeUnit.mm);
|
||||
// the glass runs the full width of the board and sits centred between the two rows of mounting
|
||||
// holes, leaving bare PCB for the header above it and a narrow lip below
|
||||
public static Size GLASS_LENGTH = new Size(69.1d, SizeUnit.mm);
|
||||
// 2.8" diagonal at 4:3, centred within the glass
|
||||
public static Size SCREEN_WIDTH = new Size(43.2d, SizeUnit.mm);
|
||||
public static Size SCREEN_LENGTH = new Size(57.6d, SizeUnit.mm);
|
||||
// clearance from the top edge to the pin row; the outline dimensions are published but this one
|
||||
// is not, so it is taken from the pitch of the 2.54mm header
|
||||
public static Size HEADER_OFFSET = new Size(3.0d, SizeUnit.mm);
|
||||
public static Size MOUNTING_HOLE_INSET = new Size(3.0d, SizeUnit.mm);
|
||||
// the pair nearest the header sits further in to clear the pin row; the other two keep the 3mm
|
||||
// inset on all sides
|
||||
public static Size MOUNTING_HOLE_HEADER_INSET = new Size(6.92d, SizeUnit.mm);
|
||||
public static Size MOUNTING_HOLE_SIZE = new Size(2.5d, SizeUnit.mm);
|
||||
public static Size CORNER_RADIUS = new Size(1.5d, SizeUnit.mm);
|
||||
|
||||
public static final String[] PIN_NAMES = new String[] {
|
||||
"VCC", "GND", "CS", "RESET", "DC", "MOSI (SDI)", "SCK", "LED", "MISO (SDO)",
|
||||
@@ -83,13 +101,23 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
return "Pin " + (index + 1);
|
||||
}
|
||||
|
||||
/** Left edge of the board, derived so that the pin row sits centred on the top edge. */
|
||||
private double getBoardX(double x) {
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
return x - (BOARD_WIDTH.convertToPixels() - (PIN_NAMES.length - 1) * spacing) / 2.0;
|
||||
}
|
||||
|
||||
private double getBoardY(double y) {
|
||||
return y - HEADER_OFFSET.convertToPixels();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControlPoints() {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
|
||||
double[][] relativeOffsets = new double[14][2];
|
||||
for (int i = 0; i < 14; i++) {
|
||||
double[][] relativeOffsets = new double[PIN_NAMES.length][2];
|
||||
for (int i = 0; i < PIN_NAMES.length; i++) {
|
||||
relativeOffsets[i][0] = i * spacing;
|
||||
relativeOffsets[i][1] = 0;
|
||||
}
|
||||
@@ -100,13 +128,9 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
@Override
|
||||
public Shape getBodyShape() {
|
||||
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 - 70;
|
||||
double boardY = y - 20;
|
||||
return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 10, 10);
|
||||
double radius = CORNER_RADIUS.convertToPixels();
|
||||
return new RoundRectangle2D.Double(getBoardX(p0.getX()), getBoardY(p0.getY()),
|
||||
BOARD_WIDTH.convertToPixels(), BOARD_HEIGHT.convertToPixels(), radius, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,8 +151,8 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
|
||||
double boardW = BOARD_WIDTH.convertToPixels();
|
||||
double boardH = BOARD_HEIGHT.convertToPixels();
|
||||
double boardX = x - 70;
|
||||
double boardY = y - 20;
|
||||
double boardX = getBoardX(x);
|
||||
double boardY = getBoardY(y);
|
||||
|
||||
Shape boardShape = getBodyShape();
|
||||
|
||||
@@ -144,33 +168,34 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
g2d.draw(boardShape);
|
||||
|
||||
if (!outlineMode) {
|
||||
// 4 Mounting holes in corners
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + 20, boardY + 20, 20);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + 20, boardY + boardH - 20, 20);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - 20, boardY + 20, 20);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - 20, boardY + boardH - 20, 20);
|
||||
double holeInset = MOUNTING_HOLE_INSET.convertToPixels();
|
||||
double headerInset = MOUNTING_HOLE_HEADER_INSET.convertToPixels();
|
||||
double holeSize = MOUNTING_HOLE_SIZE.convertToPixels();
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset, boardY + headerInset, holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - holeInset, boardY + headerInset,
|
||||
holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset, boardY + boardH - holeInset,
|
||||
holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - holeInset,
|
||||
boardY + boardH - holeInset, holeSize);
|
||||
|
||||
// Color Screen Area
|
||||
double screenMarginX = 35;
|
||||
double screenW = boardW - 2 * screenMarginX - 25;
|
||||
double screenH = boardH - 65;
|
||||
double screenX = boardX + screenMarginX;
|
||||
double screenY = boardY + 45;
|
||||
// centred between the two rows of mounting holes rather than measured from an edge, which is
|
||||
// what keeps it clear of both pairs
|
||||
double glassH = GLASS_LENGTH.convertToPixels();
|
||||
double glassY = boardY + (headerInset + boardH - holeInset) / 2.0 - glassH / 2.0;
|
||||
g2d.setColor(GLASS_COLOR);
|
||||
g2d.fill(new Rectangle2D.Double(boardX, glassY, boardW, glassH));
|
||||
|
||||
double screenW = SCREEN_WIDTH.convertToPixels();
|
||||
double screenH = SCREEN_LENGTH.convertToPixels();
|
||||
double screenX = boardX + (boardW - screenW) / 2.0;
|
||||
double screenY = glassY + (glassH - screenH) / 2.0;
|
||||
|
||||
g2d.setColor(SCREEN_BG);
|
||||
g2d.fill(new Rectangle2D.Double(screenX, screenY, screenW, screenH));
|
||||
g2d.setColor(Color.DARK_GRAY);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
g2d.draw(new Rectangle2D.Double(screenX, screenY, screenW, screenH));
|
||||
|
||||
// Display Demo UI
|
||||
g2d.setColor(Color.decode("#2980B9"));
|
||||
g2d.fillRect((int) screenX, (int) screenY, (int) screenW, 25);
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.setFont(SILK_FONT);
|
||||
StringUtils.drawCenteredText(g2d, "TFT 2.8\" 320x240 SPI", screenX + screenW / 2.0, screenY + 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
|
||||
// SD Card holder on right edge
|
||||
MakerBoardPainter.drawMetalConnector(g2d, boardX + boardW - 28, boardY + 100, 36, 120, "SD");
|
||||
}
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
@@ -183,18 +208,16 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||
g2d.setColor(TFT_RED);
|
||||
g2d.fill(new RoundRectangle2D.Double(2, 4, width - 4, height - 8, 3, 3));
|
||||
g2d.fill(new RoundRectangle2D.Double(8, 1, width - 16, height - 2, 3, 3));
|
||||
g2d.setColor(TFT_RED.darker());
|
||||
g2d.draw(new RoundRectangle2D.Double(2, 4, width - 4, height - 8, 3, 3));
|
||||
g2d.draw(new RoundRectangle2D.Double(8, 1, width - 16, height - 2, 3, 3));
|
||||
|
||||
// Screen
|
||||
g2d.setColor(SCREEN_BG);
|
||||
g2d.fillRect(6, 9, width - 12, height - 16);
|
||||
g2d.setColor(Color.decode("#2980B9"));
|
||||
g2d.fillRect(6, 9, width - 12, 5);
|
||||
g2d.fillRect(10, 8, width - 20, height - 12);
|
||||
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.setFont(new Font("SansSerif", Font.BOLD, 5));
|
||||
StringUtils.drawCenteredText(g2d, "TFT", width / 2, height / 2 + 3, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
StringUtils.drawCenteredText(g2d, "TFT", width / 2, height / 2 + 3, HorizontalAlignment.CENTER,
|
||||
VerticalAlignment.CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,12 @@ package org.diylc.components.displays;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
import org.diylc.awt.StringUtils;
|
||||
import org.diylc.common.HorizontalAlignment;
|
||||
@@ -39,6 +36,7 @@ import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.common.VerticalAlignment;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
@@ -51,19 +49,22 @@ import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
// @ComponentDescriptor(name = "NeoPixel Ring (WS2812B)", category = "Displays & Outputs",
|
||||
// author = "Branislav Stojkovic", description = "Addressable RGB WS2812B NeoPixel Ring (12/16/24 LEDs)",
|
||||
// instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
// bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
// enableCache = true)
|
||||
@ComponentDescriptor(name = "NeoPixel Ring (WS2812B)", category = "Displays & Outputs",
|
||||
author = "Branislav Stojkovic", description = "Addressable RGB WS2812B NeoPixel Ring (12/16/24 LEDs)",
|
||||
instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class WS2812BRing extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum RingSize {
|
||||
_12_LED("12 LEDs (37mm OD)", 12, 37.0, 27.0),
|
||||
_16_LED("16 LEDs (44.5mm OD)", 16, 44.5, 32.0),
|
||||
_24_LED("24 LEDs (66mm OD)", 24, 66.0, 52.0);
|
||||
// Outer and inner diameters are the figures Adafruit publishes for each ring. They are not
|
||||
// decoration: getMidRadius() averages them, and that radius places the pads, the LEDs and the
|
||||
// control points, so an inner diameter that is out by a millimetre moves every one of them.
|
||||
_12_LED("12 LEDs (36.8mm OD)", 12, 36.8, 23.3),
|
||||
_16_LED("16 LEDs (44.5mm OD)", 16, 44.5, 31.7),
|
||||
_24_LED("24 LEDs (65.5mm OD)", 24, 65.5, 52.3);
|
||||
|
||||
private final String label;
|
||||
private final int ledCount;
|
||||
@@ -83,15 +84,7 @@ public class WS2812BRing extends AbstractMakerBoard {
|
||||
public double getInnerDiameterMm() { return innerDiameterMm; }
|
||||
}
|
||||
|
||||
// Colors shared with WS2812BStick
|
||||
public static Color NEO_BLACK = Color.decode("#111111");
|
||||
public static Color LED_PACKAGE = Color.decode("#FDFEFE");
|
||||
public static Color LED_BORDER = Color.decode("#D0D3D4");
|
||||
public static Color LED_DIFFUSER = Color.decode("#EAECEE");
|
||||
public static Color DIFFUSER_BORDER = Color.decode("#BDC3C7");
|
||||
public static Color CHIP_DOT_COLOR = Color.decode("#333333");
|
||||
public static Color SOLDER_PAD_COLOR = Color.decode("#D4AC0D");
|
||||
public static Color SOLDER_PAD_BORDER = Color.decode("#9A7D0A");
|
||||
|
||||
private static final String[] PIN_NAMES = {"DIN", "+5V", "GND", "DOUT"};
|
||||
|
||||
@@ -114,6 +107,12 @@ public class WS2812BRing extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
RingSize ringSize = getRingSize();
|
||||
return ringSize == null ? null : ringSize.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
if (index >= 0 && index < PIN_NAMES.length) {
|
||||
@@ -210,32 +209,12 @@ public class WS2812BRing extends AbstractMakerBoard {
|
||||
if (!outlineMode) {
|
||||
int ledCount = ringSize.getLedCount();
|
||||
double ledSize = Math.max(8.0, Math.min(15.0, (2 * Math.PI * midR / ledCount) * 0.60));
|
||||
double halfLed = ledSize / 2.0;
|
||||
|
||||
// Draw all LEDs around the circular ring
|
||||
for (int i = 0; i < ledCount; i++) {
|
||||
double angle = 2 * Math.PI * i / ledCount - Math.PI / 2.0;
|
||||
double lx = cx + midR * Math.cos(angle);
|
||||
double ly = cy + midR * Math.sin(angle);
|
||||
|
||||
// 5050 White Package
|
||||
g2d.setColor(LED_PACKAGE);
|
||||
g2d.fill(new RoundRectangle2D.Double(lx - halfLed, ly - halfLed, ledSize, ledSize, 2, 2));
|
||||
g2d.setColor(LED_BORDER);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(0.5f));
|
||||
g2d.draw(new RoundRectangle2D.Double(lx - halfLed, ly - halfLed, ledSize, ledSize, 2, 2));
|
||||
|
||||
// Milky phosphor lens
|
||||
double diffR = halfLed * 0.7;
|
||||
g2d.setColor(LED_DIFFUSER);
|
||||
g2d.fill(new Ellipse2D.Double(lx - diffR, ly - diffR, diffR * 2, diffR * 2));
|
||||
g2d.setColor(DIFFUSER_BORDER);
|
||||
g2d.draw(new Ellipse2D.Double(lx - diffR, ly - diffR, diffR * 2, diffR * 2));
|
||||
|
||||
// Tiny IC dot
|
||||
g2d.setColor(CHIP_DOT_COLOR);
|
||||
double dotS = Math.max(2.0, diffR * 0.4);
|
||||
g2d.fill(new Rectangle2D.Double(lx - dotS / 2.0, ly - dotS / 2.0, dotS, dotS));
|
||||
MakerBoardPainter.drawAddressableLed(g2d, cx + midR * Math.cos(angle),
|
||||
cy + midR * Math.sin(angle), ledSize);
|
||||
}
|
||||
|
||||
// Center silkscreen text
|
||||
@@ -244,17 +223,8 @@ public class WS2812BRing extends AbstractMakerBoard {
|
||||
StringUtils.drawCenteredText(g2d, "NeoPixel", cx, cy - 6, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
StringUtils.drawCenteredText(g2d, ringSize.getLedCount() + "x LED", cx, cy + 6, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
|
||||
// Silkscreen labels next to the 4 solder pads on the ring
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.setFont(new Font("SansSerif", Font.BOLD, 8));
|
||||
String[] labels = new String[] {"DIN", "5V", "GND", "DOUT"};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double theta = getPadAngle(i);
|
||||
double labelR = innerR + 5;
|
||||
double px = cx + labelR * Math.cos(theta);
|
||||
double py = cy + labelR * Math.sin(theta);
|
||||
StringUtils.drawCenteredText(g2d, labels[i], px, py, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
}
|
||||
// The four pads sit one pin spacing apart along the arc, which leaves each of them far less
|
||||
// room than its name needs, so the names are left to the node tooltips and the netlist.
|
||||
}
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
|
||||
@@ -23,7 +23,6 @@ package org.diylc.components.displays;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
@@ -32,12 +31,10 @@ import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
import org.diylc.awt.StringUtils;
|
||||
import org.diylc.common.HorizontalAlignment;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.common.VerticalAlignment;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
@@ -49,30 +46,28 @@ import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
// @ComponentDescriptor(name = "NeoPixel Stick (8x WS2812B)", category = "Displays & Outputs",
|
||||
// author = "Branislav Stojkovic", description = "8-LED Addressable RGB WS2812B NeoPixel Stick",
|
||||
// instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
// bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
// enableCache = true)
|
||||
@ComponentDescriptor(name = "NeoPixel Stick (8x WS2812B)", category = "Displays & Outputs",
|
||||
author = "Branislav Stojkovic", description = "8-LED Addressable RGB WS2812B NeoPixel Stick",
|
||||
instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class WS2812BStick extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Color NEO_BLACK = Color.decode("#111111");
|
||||
public static Color LED_PACKAGE = Color.decode("#FDFEFE");
|
||||
public static Color LED_BORDER = Color.decode("#D0D3D4");
|
||||
public static Color LED_DIFFUSER = Color.decode("#EAECEE");
|
||||
public static Color DIFFUSER_BORDER = Color.decode("#BDC3C7");
|
||||
public static Color CHIP_DOT_COLOR = Color.decode("#333333");
|
||||
|
||||
public static Font PIN_LABEL_FONT = new Font("SansSerif", Font.BOLD, 9);
|
||||
|
||||
public static Size BOARD_WIDTH = new Size(53.0d, SizeUnit.mm);
|
||||
public static Size BOARD_HEIGHT = new Size(10.2d, SizeUnit.mm);
|
||||
public static Size BOARD_WIDTH = new Size(51.1d, SizeUnit.mm);
|
||||
public static Size BOARD_HEIGHT = new Size(10.22d, SizeUnit.mm);
|
||||
public static Size LED_SIZE = new Size(5.0d, SizeUnit.mm);
|
||||
public static Size PAD_INSET = new Size(2.0d, SizeUnit.mm);
|
||||
|
||||
// Each end carries four pads in the order GND, data, power, GND. Both grounds on an end are the
|
||||
// same net on the board, so they are numbered only to keep the node names distinct; the
|
||||
// silkscreen prints "GND" for all four.
|
||||
private static final String[] PIN_NAMES = {
|
||||
"DIN", "+5V (In)", "GND (In)", "GND (In)",
|
||||
"DOUT", "+5V (Out)", "GND (Out)", "GND (Out)"
|
||||
"GND_1", "DIN", "+5V_1", "GND_2",
|
||||
"GND_3", "DOUT", "+5V_2", "GND_4"
|
||||
};
|
||||
|
||||
public WS2812BStick() {
|
||||
@@ -93,33 +88,36 @@ public class WS2812BStick extends AbstractMakerBoard {
|
||||
protected void updateControlPoints() {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
double w = BOARD_WIDTH.convertToPixels();
|
||||
// the two pad columns sit the same distance in from each end of the board
|
||||
double columnSpacing = BOARD_WIDTH.convertToPixels() - 2 * PAD_INSET.convertToPixels();
|
||||
|
||||
// 8 pins total:
|
||||
// Left input header (4 pins): 0 = DIN, 1 = +5V, 2 = GND, 3 = GND
|
||||
// Right output header (4 pins): 4 = DOUT, 5 = +5V, 6 = GND, 7 = GND
|
||||
double[][] relativeOffsets = new double[][] {
|
||||
{ 0, 0 },
|
||||
{ 0, spacing },
|
||||
{ 0, spacing * 2 },
|
||||
{ 0, spacing * 3 },
|
||||
{ w - 20, 0 },
|
||||
{ w - 20, spacing },
|
||||
{ w - 20, spacing * 2 },
|
||||
{ w - 20, spacing * 3 }
|
||||
};
|
||||
double[][] relativeOffsets = new double[PIN_NAMES.length][2];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
relativeOffsets[i][0] = 0;
|
||||
relativeOffsets[i][1] = i * spacing;
|
||||
relativeOffsets[4 + i][0] = columnSpacing;
|
||||
relativeOffsets[4 + i][1] = i * spacing;
|
||||
}
|
||||
|
||||
rotatePoints(firstPoint, relativeOffsets);
|
||||
}
|
||||
|
||||
/** Left edge of the board; the first pad column sits {@code PAD_INSET} in from it. */
|
||||
private double getBoardX(double x) {
|
||||
return x - PAD_INSET.convertToPixels();
|
||||
}
|
||||
|
||||
/** Top edge of the board, derived so that the four-pad column is centred across its width. */
|
||||
private double getBoardY(double y) {
|
||||
double padSpan = 3 * PIN_SPACING.convertToPixels();
|
||||
return y - (BOARD_HEIGHT.convertToPixels() - padSpan) / 2.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getBodyShape() {
|
||||
Point2D p0 = controlPoints[0];
|
||||
double x = p0.getX();
|
||||
double y = p0.getY();
|
||||
double boardW = BOARD_WIDTH.convertToPixels();
|
||||
double boardH = BOARD_HEIGHT.convertToPixels();
|
||||
return new RoundRectangle2D.Double(x - 10, y - 10, boardW, boardH, 4, 4);
|
||||
return new RoundRectangle2D.Double(getBoardX(p0.getX()), getBoardY(p0.getY()),
|
||||
BOARD_WIDTH.convertToPixels(), BOARD_HEIGHT.convertToPixels(), 4, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,8 +138,8 @@ public class WS2812BStick extends AbstractMakerBoard {
|
||||
|
||||
double boardW = BOARD_WIDTH.convertToPixels();
|
||||
double boardH = BOARD_HEIGHT.convertToPixels();
|
||||
double boardX = x - 10;
|
||||
double boardY = y - 10;
|
||||
double boardX = getBoardX(x);
|
||||
double boardY = getBoardY(y);
|
||||
|
||||
Shape boardShape = getBodyShape();
|
||||
|
||||
@@ -157,49 +155,14 @@ public class WS2812BStick extends AbstractMakerBoard {
|
||||
g2d.draw(boardShape);
|
||||
|
||||
if (!outlineMode) {
|
||||
// 8x 5050 SMD WS2812B RGB LEDs (centered between left and right pin areas)
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
double ledAreaStartX = boardX + 68;
|
||||
double ledAreaEndX = boardX + boardW - 68;
|
||||
double ledPitch = (ledAreaEndX - ledAreaStartX) / 7.0;
|
||||
// Eight 5mm packages in a row, centred along the board. The pad names are printed on the back
|
||||
// of the real board, so this face carries no silkscreen.
|
||||
double ledSize = LED_SIZE.convertToPixels();
|
||||
double fieldInset = (boardW - 8 * ledSize) / 2.0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double lx = ledAreaStartX + i * ledPitch;
|
||||
double ly = boardY + boardH / 2.0;
|
||||
|
||||
// 5050 White Package (28x28)
|
||||
g2d.setColor(LED_PACKAGE);
|
||||
g2d.fill(new RoundRectangle2D.Double(lx - 14, ly - 14, 28, 28, 3, 3));
|
||||
g2d.setColor(LED_BORDER);
|
||||
g2d.draw(new RoundRectangle2D.Double(lx - 14, ly - 14, 28, 28, 3, 3));
|
||||
|
||||
// Circular milky phosphor/lens
|
||||
g2d.setColor(LED_DIFFUSER);
|
||||
g2d.fill(new Ellipse2D.Double(lx - 10, ly - 10, 20, 20));
|
||||
g2d.setColor(DIFFUSER_BORDER);
|
||||
g2d.draw(new Ellipse2D.Double(lx - 10, ly - 10, 20, 20));
|
||||
|
||||
// Internal tiny IC dot
|
||||
g2d.setColor(CHIP_DOT_COLOR);
|
||||
g2d.fill(new Rectangle2D.Double(lx - 2.5, ly - 2.5, 5, 5));
|
||||
}
|
||||
|
||||
// Silk Screen Pin Labels — all 4 pins labeled on both sides with clean spacing
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.setFont(PIN_LABEL_FONT);
|
||||
|
||||
// Left Input Header Labels (DIN, 5V, GND, GND)
|
||||
String[] inLabels = {"DIN", "5V", "GND", "GND"};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
StringUtils.drawCenteredText(g2d, inLabels[i], boardX + 23, y + i * spacing,
|
||||
HorizontalAlignment.LEFT, VerticalAlignment.CENTER);
|
||||
}
|
||||
|
||||
// Right Output Header Labels (DOUT, 5V, GND, GND)
|
||||
String[] outLabels = {"DOUT", "5V", "GND", "GND"};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
StringUtils.drawCenteredText(g2d, outLabels[i], boardX + boardW - 23, y + i * spacing,
|
||||
HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
|
||||
MakerBoardPainter.drawAddressableLed(g2d, boardX + fieldInset + (i + 0.5) * ledSize,
|
||||
boardY + boardH / 2.0, ledSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -262,6 +262,11 @@ public class ArduinoNano extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getVersion().toString();
|
||||
}
|
||||
|
||||
private String[] getPinNames() {
|
||||
switch (getVersion()) {
|
||||
case EVERY:
|
||||
|
||||
@@ -231,6 +231,11 @@ public class ArduinoUno extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getVersion().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* True for the Renesas RA4M1 generation, which shares a USB-C connector and the RA4M1 in place of
|
||||
* the ATmega328P.
|
||||
|
||||
@@ -354,6 +354,11 @@ public class ESP32DevKit extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getVersion().toString();
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Headers")
|
||||
public boolean getHeaders() {
|
||||
return headers;
|
||||
|
||||
@@ -113,6 +113,12 @@ public class RaspberryPi extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
RaspberryPiVersion version = getVersion();
|
||||
return version == null ? null : version.toString();
|
||||
}
|
||||
|
||||
public RaspberryPi() {
|
||||
super();
|
||||
this.bodyColor = RPI_GREEN;
|
||||
|
||||
@@ -139,6 +139,11 @@ public class RaspberryPiPico extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getVersion().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* True for the wireless variants, which carry the CYW43439 radio and move the SWD pads out of
|
||||
* the bottom edge to make room for it.
|
||||
|
||||
@@ -115,6 +115,11 @@ public class RaspberryPiZero extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getVersion().toString();
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Headers")
|
||||
public boolean getHeaders() {
|
||||
return headers;
|
||||
|
||||
@@ -238,6 +238,11 @@ public class Teensy extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getVersion().toString();
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Headers")
|
||||
public boolean getHeaders() {
|
||||
return headers;
|
||||
|
||||
+10
-10
@@ -19,7 +19,7 @@
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.micro;
|
||||
package org.diylc.components.maker;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Point2D;
|
||||
@@ -40,12 +40,12 @@ import org.junit.Assert;
|
||||
*
|
||||
* <p>What every board has in common -- naming all of its pins, keeping them apart, drawing in each
|
||||
* state, rotating -- is asserted once for all of them in {@code MakerComponentsTest}, but only for
|
||||
* the version each board starts with. The tests in this package cover what is specific to a board:
|
||||
* the version each board starts with. The per-board tests cover what is specific to a board:
|
||||
* its pinout, its geometry and the way those change from one version to the next.
|
||||
*/
|
||||
class MakerBoardTestSupport {
|
||||
public class MakerBoardTestSupport {
|
||||
|
||||
static final double PIN_SPACING = new Size(0.1d, SizeUnit.in).convertToPixels();
|
||||
public static final double PIN_SPACING = new Size(0.1d, SizeUnit.in).convertToPixels();
|
||||
|
||||
private static final Project PROJECT = new Project();
|
||||
|
||||
@@ -73,7 +73,7 @@ class MakerBoardTestSupport {
|
||||
};
|
||||
|
||||
/** Asserts that pins {@code from} through {@code to} form a straight row on 0.1" pitch. */
|
||||
static void assertRow(AbstractMakerBoard board, int from, int to) {
|
||||
public static void assertRow(AbstractMakerBoard board, int from, int to) {
|
||||
assertRow(board, from, to, PIN_SPACING);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class MakerBoardTestSupport {
|
||||
* Asserts that pins {@code from} through {@code to} form a straight row on the given pitch. The
|
||||
* axis comes from the first pair, so a row that bends part way along fails.
|
||||
*/
|
||||
static void assertRow(AbstractMakerBoard board, int from, int to, double pitch) {
|
||||
public static void assertRow(AbstractMakerBoard board, int from, int to, double pitch) {
|
||||
boolean vertical = Math.abs(
|
||||
board.getControlPoint(from).getX() - board.getControlPoint(from + 1).getX()) < 0.01;
|
||||
for (int i = from; i < to; i++) {
|
||||
@@ -95,7 +95,7 @@ class MakerBoardTestSupport {
|
||||
}
|
||||
|
||||
/** Asserts the distance between the first pins of two parallel rows. */
|
||||
static void assertRowSpacing(AbstractMakerBoard board, int first, int second, Size spacing) {
|
||||
public static void assertRowSpacing(AbstractMakerBoard board, int first, int second, Size spacing) {
|
||||
Point2D p1 = board.getControlPoint(first);
|
||||
Point2D p2 = board.getControlPoint(second);
|
||||
Assert.assertEquals("Row spacing", spacing.convertToPixels(), p1.distance(p2), 0.01);
|
||||
@@ -103,7 +103,7 @@ class MakerBoardTestSupport {
|
||||
}
|
||||
|
||||
/** Asserts the outline size, which is what a shield, a case or an enclosure has to fit. */
|
||||
static void assertBoardSize(AbstractMakerBoard board, Size width, Size length) {
|
||||
public static void assertBoardSize(AbstractMakerBoard board, Size width, Size length) {
|
||||
Rectangle2D bounds = board.getBodyShape().getBounds2D();
|
||||
Assert.assertEquals("Board width", width.convertToPixels(), bounds.getWidth(), 0.1);
|
||||
Assert.assertEquals("Board length", length.convertToPixels(), bounds.getHeight(), 0.1);
|
||||
@@ -114,7 +114,7 @@ class MakerBoardTestSupport {
|
||||
* control points in the same place. A revision that keeps its footprint has to keep its pin
|
||||
* positions too, or switching the version moves every connection in an existing file.
|
||||
*/
|
||||
static void assertSharedFootprint(AbstractMakerBoard reference, AbstractMakerBoard variant,
|
||||
public static void assertSharedFootprint(AbstractMakerBoard reference, AbstractMakerBoard variant,
|
||||
int sharedPins) {
|
||||
Assert.assertEquals("Outline", reference.getBodyShape().getBounds2D(),
|
||||
variant.getBodyShape().getBounds2D());
|
||||
@@ -124,7 +124,7 @@ class MakerBoardTestSupport {
|
||||
}
|
||||
|
||||
/** Draws the board in every state and orientation; anything that throws fails the test. */
|
||||
static void assertDrawsCleanly(AbstractMakerBoard board) {
|
||||
public static void assertDrawsCleanly(AbstractMakerBoard board) {
|
||||
BufferedImage image = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
Orientation original = board.getOrientation();
|
||||
+9
-9
@@ -72,6 +72,15 @@ public class MakerComponentsTest {
|
||||
ESP8266NodeMCU.class,
|
||||
WemosD1Mini.class,
|
||||
|
||||
// Displays & Outputs
|
||||
CharacterLCD.class,
|
||||
OLEDDisplay.class,
|
||||
SevenSegmentDisplay.class,
|
||||
LEDMatrix8x8.class,
|
||||
TFTDisplay.class,
|
||||
WS2812BStick.class,
|
||||
WS2812BRing.class,
|
||||
|
||||
// Electro-Mechanical
|
||||
BatteryHolder18650.class
|
||||
);
|
||||
@@ -90,15 +99,6 @@ public class MakerComponentsTest {
|
||||
TCRT5000Sensor.class,
|
||||
IRReceiverModule.class,
|
||||
|
||||
// Displays & Outputs
|
||||
CharacterLCD.class,
|
||||
OLEDDisplay.class,
|
||||
SevenSegmentDisplay.class,
|
||||
LEDMatrix8x8.class,
|
||||
TFTDisplay.class,
|
||||
WS2812BStick.class,
|
||||
WS2812BRing.class,
|
||||
|
||||
// Modules & Breakouts
|
||||
LogicLevelConverter.class,
|
||||
L298NMotorDriver.class,
|
||||
|
||||
@@ -23,6 +23,8 @@ package org.diylc.components.micro;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
package org.diylc.components.micro;
|
||||
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.components.micro.ArduinoNano.NanoVersion;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
@@ -24,6 +24,7 @@ package org.diylc.components.micro;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.components.micro.ArduinoUno.ArduinoUnoVersion;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -24,6 +24,7 @@ package org.diylc.components.micro;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.components.micro.ESP32DevKit.DevKitVersion;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
@@ -23,6 +23,7 @@ package org.diylc.components.micro;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ package org.diylc.components.micro;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.components.micro.RaspberryPiPico.PicoVersion;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
@@ -24,6 +24,7 @@ package org.diylc.components.micro;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.components.micro.RaspberryPi.RaspberryPiVersion;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
@@ -24,6 +24,7 @@ package org.diylc.components.micro;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.components.micro.RaspberryPiZero.ZeroVersion;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
package org.diylc.components.micro;
|
||||
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.components.micro.Teensy.TeensyVersion;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
@@ -23,6 +23,7 @@ package org.diylc.components.micro;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
# Maker Peripherals
|
||||
|
||||
Releasing the staged `Displays & Outputs` components, and closing the gaps that would stop
|
||||
someone drawing a common display.
|
||||
|
||||
Status: proposed
|
||||
Scope: `diylc-library`, package `org.diylc.components.displays`
|
||||
Companion to: `maker-board-roadmap.md` (which covers `org.diylc.components.micro` only)
|
||||
|
||||
## 1. Goal
|
||||
|
||||
The first maker slice shipped ten controller boards in 6.5.0. Forty-one further components sit in
|
||||
the tree with their `@ComponentDescriptor` annotations commented out, across four packages:
|
||||
`displays` (7), `sensors` (9), `modules` (21) and `robotics` (4). None of them is discoverable, and
|
||||
`MakerComponentsTest` lists all forty-one in `unreleasedMakerComponentClasses`.
|
||||
|
||||
This document plans the release of the **seven display components**, the fixes they need first, and
|
||||
the additions that make the category cover the market rather than sample it. Sensors, modules and
|
||||
robotics are deliberately deferred; §10 records why.
|
||||
|
||||
## 2. Decisions that govern this slice
|
||||
|
||||
| # | Decision | Choice | Rationale |
|
||||
|---|---|---|---|
|
||||
| D1 | Which package ships second | **`displays` only.** | Displays are archetype-complete in a way sensors are not: the hobby world has roughly seven display archetypes and the drafts already cover all seven, whereas the nine drafted sensors are a sample of hundreds with no canonical members. Displays also reach the *existing* guitar and amp audience — 7-segment counters, character LCDs and addressable strips appear in builds with no microcontroller anywhere. |
|
||||
| D2 | How coverage grows inside an archetype | **A `Version`-style enum on the existing class where the footprint family is shared; a new class where it is not.** | Same rule as the board roadmap's D2. `TFTDisplay` and `LEDMatrix8x8` currently have no variant enum at all, which is the largest structural gap in the package. |
|
||||
| D3 | Where dimension and pin data live | **Hardcoded `Size` constants and `String[]` arrays in the component class,** per-variant where a variant changes them. | Matches every shipped board. No pin-definition resource is introduced. |
|
||||
| D4 | Screen content | **A neutral dark panel, with at most a small part-number silk.** No faked user interface. | Four drafts currently render English demo text ("HELLO WORLD! 16x2", "OLED DISPLAY") at fixed font sizes, which does not scale with the board, bakes a language into a drawing, and dates quickly. A dark glass panel is what the part looks like when it is not powered, which is how every other component is drawn. |
|
||||
| D5 | Shared LED rendering | **One helper in `MakerBoardPainter`,** used by every addressable-RGB part. | `WS2812BRing` and `WS2812BStick` already contain two copies of the 5050-package-plus-diffuser-plus-IC-dot drawing, one scaled and one in raw pixels. A strip and a panel would make four. |
|
||||
| D6 | Backward compatibility | Nothing here is a compatibility risk. | These classes have never been discoverable, so no `.diy` file references them. Field names, control-point counts and defaults can still be changed freely — **this slice is the last chance to do so.** |
|
||||
| D7 | Geometry expressed in `Size` | **Every dimension derives from a `Size` constant.** | The drafts place bezels, screens and mounting holes with raw pixel offsets (`boardX = x - 60`, `bezelY = boardY + 45`), so nothing moves if a board dimension is corrected. The shipped boards do not do this. |
|
||||
|
||||
### Explicitly out of scope
|
||||
|
||||
- Sensors, modules and robotics (§10).
|
||||
- The `AbstractMotor` / `AbstractMakerBoard` duplication, which only `robotics` suffers from.
|
||||
- The board roadmap's open decisions on BOM variant treatment and a uniform `Headers` property set
|
||||
(§11 restates them, because this slice adds variants that make them more pressing).
|
||||
- Bumping the project version in the POMs.
|
||||
|
||||
## 3. Current coverage
|
||||
|
||||
Seven classes, all `category = "Displays & Outputs"`, all `bomPolicy = SHOW_ONLY_TYPE_NAME`, all
|
||||
`enableCache = true`, all extending `AbstractMakerBoard`.
|
||||
|
||||
| Class | Variants today | Assessment |
|
||||
|---|---|---|
|
||||
| `SevenSegmentDisplay` | 1-digit 10-pin, 4-digit 12-pin, TM1637 module | **Best-*written* file in the set**, which is not the same as the best-sourced one. The segment-mask table is genuinely good, the 10-pin structure is corroborated, and the glyph itself is now the standard unit-grid construction: an upright 10 × 18 box with segments two units thick, mitred at 45 degrees so neighbours meet at a shared vertex, each outlined in the face colour so that they still read as separate segments. That replaced a set of hand-tuned polygons and retired both `SEGMENT_GAP` (the construction separates by outline rather than by gap) and `SLANT_DEGREES` (it is drawn upright, as the source is). What remains unchecked is everything *around* the glyph. The per-package dimensions now live on the `DisplayType` constants rather than in scattered branches, and the enum has been split so that the two bare four-digit packages — 0.36" and 0.56" — are separate variants sharing one pin array. The 0.56" set is what this class has always carried and remains unverified; the 0.36" set is now largely maintainer-supplied — body 30 × 14 mm, 7.5 mm digit pitch, 0.4" row spacing — leaving only its digit width inferred. See §11.10 and §11.11. |
|
||||
| `WS2812BRing` | 12 / 16 / 24 LED | **Good.** Genuine polar maths placing pads on the mid-radius arc, mm-based OD/ID per size, LED count driving the rendering, `drawSolderPads` rather than a header — correct for a ring. |
|
||||
| `CharacterLCD` | 16x2 / 20x4, I2C backpack / 16-pin parallel | Correct HD44780 and PCF8574 pin data, correct 80 × 36 and 98 × 60 mm bodies. All screen and bezel geometry in raw pixels, including an eyeballed `bezelMarginX = (16x2) ? 45 : 40`. Allocates a `Font` inside `draw()`. |
|
||||
| `OLEDDisplay` | I2C 4-pin / SPI 7-pin | Correct pin names and a correct 27 mm square body, but **the glass is drawn near-square** when the 0.96" SSD1306 active area is a roughly 2:1 letterbox. No size variants. |
|
||||
| `LEDMatrix8x8` | none | Plausible single MAX7219 module with correct cascade headers, but a hardcoded `-44 mm` output-header offset and pixel-placed matrix and chip. **Missing the 4-in-1 32x8 module.** |
|
||||
| `TFTDisplay` | none | **Weakest file.** No variant enum. Name and descriptor say 240x320 while the on-screen silk says `320x240`. See the footprint bug below. |
|
||||
| `WS2812BStick` | none | Hardcoded to 8 LEDs, with every LED dimension in raw pixels (28 × 28 package, 68 px insets), so the LEDs do not scale with the board. See the node-name bug below. |
|
||||
|
||||
### 3.1 Two defects that are bugs, not style
|
||||
|
||||
**`TFTDisplay` header edge.** The 14-pin header is laid out along the 86 mm edge, but on the real
|
||||
2.8" ILI9341 module that header runs along the short 50 mm edge — 14 pins on 0.1" pitch is 33 mm,
|
||||
which fits in 50 mm and not in 86 mm. The module is portrait and the draft draws it landscape. The
|
||||
board outline, the header edge and the screen aspect all need re-deriving together from the module
|
||||
drawing.
|
||||
|
||||
**`WS2812BStick` duplicate node names.** `PIN_NAMES` declares `GND (In)` twice and `GND (Out)`
|
||||
twice, so each end of the board carries two identically named nodes. This is the same duplicate-node
|
||||
problem that was just fixed for building-block terminals, and it will reach the netlist. The real
|
||||
pad count per end has to come from the product drawing; the draft's four-per-end with a repeated
|
||||
ground is almost certainly wrong.
|
||||
|
||||
### 3.2 Cross-cutting items
|
||||
|
||||
1. **`zOrder` splits the package.** `CharacterLCD` and `TFTDisplay` declare `IDIYComponent.BOARD`;
|
||||
the other five declare `COMPONENT`. A display sits on top of a board rather than being one, so
|
||||
`COMPONENT` is right for all seven. Same class of oversight as the board roadmap's §8.1 Teensy
|
||||
item.
|
||||
2. **No file uses the shared silk machinery.** `drawPinLabels` / `getSilkPinLabel` exist on
|
||||
`AbstractMakerBoard` and four shipped boards use them; here `WS2812BStick` and `WS2812BRing`
|
||||
hand-roll label arrays that duplicate their own `PIN_NAMES` and will drift from them.
|
||||
3. **Fonts allocated inside `draw()`** in `CharacterLCD` (`new Font("Monospaced", ...)`) and
|
||||
`WS2812BRing` (`new Font("SansSerif", BOLD, 8)`). `draw()` runs on every repaint; the convention
|
||||
is a cached static.
|
||||
4. **Non-ASCII characters** in `SevenSegmentDisplay` and `WS2812BStick`. Source encoding is
|
||||
ISO-8859-1 and all ten shipped `micro` classes are clean, so this arrived with the drafts.
|
||||
5. **Unused `java.awt.Font` import** in `LEDMatrix8x8`.
|
||||
6. **The NeoPixel Ring's pad labels collide** — **resolved by dropping them.** They were drawn at
|
||||
`innerR + 5`, and because the four pads sit one pin spacing apart along the arc, each label had
|
||||
roughly 15 px of arc for 24-30 px of text. No radius or font size fixes that: a larger radius
|
||||
spreads the labels but they are anchored to pads that do not move, and a smaller font stops
|
||||
being legible long before it stops overlapping. Moving them away from their pads would defeat
|
||||
the point of a pad label, so the names are left to the node tooltips and the netlist, as on the
|
||||
Stick. The array also duplicated `PIN_NAMES` and had already drifted from it, printing `5V`
|
||||
where the node name says `+5V`.
|
||||
7. **Connectors are not centred on their edge.** In `CharacterLCD` and `TFTDisplay` the header
|
||||
starts at the control-point origin while the board is placed by a raw pixel offset (−60 and −70
|
||||
respectively), so the connector sits in a corner instead of centred where the product has it.
|
||||
`OLEDDisplay` was wrongly included here at first: its `boardX` formula does centre the pin row,
|
||||
and only its vertical offset was a raw pixel value. This is the visible face of D7, and it
|
||||
resolves when the geometry is re-derived from `Size` constants in item 6.
|
||||
|
||||
8. **The Ring's pad arc and LED arc coincide.** Both the solder pads and the LEDs are placed on the
|
||||
mid-radius circle, so the four pads sit on top of LEDs at the bottom of the ring. Nothing is
|
||||
hidden — `drawSolderPads` runs last, after the transform is restored, so the pads draw over the
|
||||
LEDs — but on the real board the pads occupy a gap in the LED sequence rather than sharing it.
|
||||
Whether the LED ring should skip the pad positions is a fidelity question for item 10.
|
||||
|
||||
9. **`LEDMatrix8x8`'s `OUT` label sits inside the matrix block.** The output header row is 3.46 mm
|
||||
below the top edge and the matrix starts 3.8 mm below it, so the strip between them is too
|
||||
narrow for the label at any offset: placing it below the row puts it 1.66 mm inside the matrix,
|
||||
and placing it above the row runs it into the board edge. This predates the `Size`-constant
|
||||
rewrite — the previous raw offset put it 0.5 mm inside the same block — so the rewrite preserved
|
||||
the overlap rather than causing it. Fixing it needs the real module's silkscreen layout relative
|
||||
to its matrix, which is item 10.
|
||||
|
||||
10. **Every one of the Ring's six diameters was wrong.** The enum carried 37.0/27.0, 44.5/32.0 and
|
||||
66.0/52.0 for the 12, 16 and 24 LED rings; Adafruit publishes 36.8/23.3, 44.5/31.7 and
|
||||
65.5/52.3. The outer diameters were near-misses but the inner ones were not — the 12-LED ring
|
||||
was out by 3.7 mm on a 36.8 mm part. That is not cosmetic: `getMidRadius()` averages the two,
|
||||
and that radius places the pads, the LEDs and the control points, so the 12-ring's geometry sat
|
||||
about a millimetre out everywhere. This was the first substantive error item 10 turned up, and
|
||||
it is the argument for finishing the sweep rather than trusting the remaining drafts.
|
||||
|
||||
Items 6 through 9 were found by rendering the components, not by reading them; item 10 was found by
|
||||
checking a vendor page. Since no regression
|
||||
baseline exists, an offscreen render harness — instantiate by class name, draw into a
|
||||
`BufferedImage` sized from `getCachingBounds()`, write a PNG per component — is the cheapest way to
|
||||
see this class of defect, and it works whether or not the descriptors are enabled. Note that moving
|
||||
such a component requires shifting **every** control point: `setControlPoint(p, 0)` moves only that
|
||||
point and does not re-run `updateControlPoints()`, so the body follows point 0 while the remaining
|
||||
pins stay behind.
|
||||
|
||||
## 4. Shared mechanics
|
||||
|
||||
The board roadmap's §4 applies unchanged and is not repeated: component discovery is a classpath
|
||||
annotation scan with no registry to edit, the `Version` enum pattern and its null-guard, XStream
|
||||
serialising enum constants by name, and the sourcing rules in §4.5. What differs for this slice:
|
||||
|
||||
**Test support has to be promoted.** `MakerBoardTestSupport` is package-private in
|
||||
`org.diylc.components.micro`, so a `displays` test package cannot see its `assertRow`,
|
||||
`assertRowSpacing`, `assertBoardSize` and `assertSharedFootprint` helpers or its shared no-op
|
||||
`IDrawingObserver`. Move it to `org.diylc.components.maker` beside `MakerComponentsTest` and make it
|
||||
public, updating the ten `micro` tests' imports. Do this first; everything in §7 depends on it.
|
||||
|
||||
**One new painter helper.** `MakerBoardPainter.drawAddressableLed(g2d, cx, cy, size)` drawing the
|
||||
5050 white package, the milky diffuser and the internal IC dot, all derived from `size`. Port
|
||||
`WS2812BRing` onto it (its version is already correct, so its rendering must not change), then
|
||||
`WS2812BStick`, before any new addressable part is written.
|
||||
|
||||
**Release mechanics.** Uncommenting a `@ComponentDescriptor` is what ships a component. Each release
|
||||
also needs the class moved from `unreleasedMakerComponentClasses` to `releasedMakerComponentClasses`
|
||||
in `MakerComponentsTest`, so that the discovery and icon assertions start covering it.
|
||||
|
||||
## 5. Prep pass on the existing seven
|
||||
|
||||
Ordered by risk, not by file size. None of these is a compatibility concern (D6).
|
||||
|
||||
| # | Work | Files | Rough effort |
|
||||
|---|---|---|---|
|
||||
| 1 | Promote `MakerBoardTestSupport` to `org.diylc.components.maker`, make it public, fix the ten `micro` imports — **done** | 11 test files | 1 h |
|
||||
| 2 | Add `MakerBoardPainter.drawAddressableLed`; port `Ring` (no visual change) then `Stick` — **done** | 3 | 2-3 h |
|
||||
| 3 | Re-derive the `TFTDisplay` footprint from the module drawing: outline, header edge, screen aspect, SD-slot position (§3.1) — **done**: portrait 50 × 86 mm, header centred on the short edge, screen derived from the glass, four holes placed from the vendor drawing and the glass centred between them. The front-face SD slot was removed rather than moved, because the reader is on the back | 1 | 3-4 h |
|
||||
| 4 | Fix `WS2812BStick` pad names and count from the product drawing; rebuild its geometry from `Size` constants (§3.1) — **done**, with one correction to the premise: the **count was already right**. Four pads per end is what the board has, and the doubled ground is real hardware on one net. Only the *order* was wrong, so the pads were reordered to GND, data, power, GND and renamed `GND_1`…`GND_4` for uniqueness. Board corrected to 51.1 × 10.22 mm with real 5 mm packages | 1 | 2-3 h |
|
||||
| 5 | Fix the `OLEDDisplay` glass aspect to the real active area — **done**: panel 26.7 × 19.3 mm centred between the hole rows, with the 21.744 × 10.864 mm lit area drawn inside it | 1 | 1 h |
|
||||
| 6 | Replace raw pixel geometry with `Size` constants (D7) — **done for `OLEDDisplay`, `TFTDisplay`, `LEDMatrix8x8` and `WS2812BStick`; blocked for `CharacterLCD`**, see §11.8 | `CharacterLCD`, `OLEDDisplay`, `TFTDisplay`, `LEDMatrix8x8`, `WS2812BStick` | 4-6 h |
|
||||
| 7 | Replace demo screen text with a neutral panel (D4) — **done**: all three now draw an unpowered panel, and `CharacterLCD`'s `SCREEN_TEXT` colour went with its lettering | `CharacterLCD`, `OLEDDisplay`, `TFTDisplay` | 1-2 h |
|
||||
| 8 | Route silk labels through `getSilkPinLabel` / `drawPinLabels` — **done**: the Stick's now come from `getSilkPinLabel` and the Ring's are dropped; removing the Ring's inline `new Font(...)` also settles half of item 9 | `WS2812BStick`, `WS2812BRing` | 1-2 h |
|
||||
| 9 | `zOrder` to `COMPONENT` on `CharacterLCD` and `TFTDisplay`; cache the two `draw()` fonts; strip non-ASCII; drop the unused import — **done**: all seven are `COMPONENT`, both `draw()` fonts left with items 7 and 8, `SevenSegmentDisplay`'s degree signs were the last non-ASCII, and the orphaned imports plus `CharacterLCD.SCREEN_TEXT` are gone. The three surviving `new Font(...)` calls are all in `drawIcon`, which runs per toolbox icon rather than per repaint, so they are deliberately left | 5 | 1 h |
|
||||
| 10 | Cross-check every surviving dimension and pin array against vendor documentation — **partly done.** Closed: `TFTDisplay` (outline, active area, header edge, hole positions, glass placement), `WS2812BStick` (board, 5050 package, pad count and order), `OLEDDisplay` (panel, lit area, and a 24 mm centre-to-centre hole pattern in both directions, modelled as spacing rather than an edge inset since that is how it is specified and what a builder drills), and `WS2812BRing`'s diameters — **all six of which were wrong**, see §3.2 item 10. Open: the Ring's pad count (§11.9), `SevenSegmentDisplay`'s pin arrays (§11.10), `CharacterLCD`'s part identity (§11.8), `LEDMatrix8x8`'s silk placement (§3.2 item 9), and the Ring's pad/LED arc sharing (§3.2 item 8) | all 7 | the bulk of the slice |
|
||||
|
||||
Items 1 and 2 have landed. The five shared 5050-package colours now live on `AbstractMakerBoard` as
|
||||
`RGB_LED_*`, and the duplicated copies are gone from both NeoPixel classes along with two dead
|
||||
constants (`SOLDER_PAD_COLOR` / `SOLDER_PAD_BORDER`) that nothing referenced. `WS2812BRing` renders
|
||||
identically, because the helper reproduces its geometry exactly; `WS2812BStick` changed very
|
||||
slightly — corner radius, lens diameter and border stroke now match the ring, which is the intended
|
||||
consequence of there being one renderer. The stick's LED size is still the literal 28 px it always
|
||||
was, since deriving it from the real 5.0 mm package changes the drawing and belongs to item 4.
|
||||
|
||||
Items 3 and 4 have largely landed, against sourced figures. The TFT module is **50 × 86 mm
|
||||
portrait** with a **43.2 × 57.6 mm** active area, and its fourteen pin names and their order already
|
||||
matched the product, so only the geometry was wrong: the outline is now portrait, the header is
|
||||
centred on the short top edge, the screen is derived from the glass rather than from pixel offsets,
|
||||
and the front-face microSD block is gone because the reader is on the back. The NeoPixel Stick is
|
||||
**51.1 × 10.22 mm** with **5.0 mm** packages, and its four-pads-per-end turned out to be correct —
|
||||
the order was not. Each end reads GND, data, power, GND, and the doubled ground is real hardware on
|
||||
one net, so the pads are now named `GND_1`…`GND_4` and the silkscreen prints "GND" for all four via
|
||||
`getSilkPinLabel`. **The control-point count stays at 8.**
|
||||
|
||||
Both of the items these left open have since been settled from the vendor drawing and are recorded
|
||||
in §11.5 and §11.6. The TFT carries **four** mounting holes, all 3 mm in from the side edges, with
|
||||
the pair nearest the header 6.92 mm down from that edge to clear the pin row and the lower pair
|
||||
3 mm up from the bottom. They are drawn after the glass so that all four stay visible. The Stick's
|
||||
front face now carries no silkscreen at all, which is what the real board does — its pad names are
|
||||
printed on the back — and with the labels gone the LED row takes its true layout, eight 5 mm
|
||||
packages centred, with the inset derived rather than tuned.
|
||||
|
||||
The glass turned out to be centred between the two rows of mounting holes rather than measured from
|
||||
an edge, which resolves the apparent conflict: its midpoint is 44.96 mm from the top, so a 69.1 mm
|
||||
glass spans 10.41 mm to 79.51 mm and clears both pairs. The drawing derives it that way rather than
|
||||
storing another offset constant, so it stays correct if a hole position is ever revised.
|
||||
|
||||
Item 10 is the real cost and cannot be shortcut. These classes were drafted without sources, so
|
||||
**every number in them is an estimate until checked**, including the ones this document quotes as
|
||||
correct. What works as a source: Adafruit product pages and their EagleCAD/Fritzing footprints for
|
||||
the NeoPixel parts; the SSD1306, SH1106, ILI9341, ST7735, ST7789, PCD8544, MAX7219 and TM1637
|
||||
datasheets for controllers; generic module drawings from the AliExpress or Waveshare listing for the
|
||||
red SPI TFT and the I2C backpack, which have no canonical vendor.
|
||||
|
||||
## 6. Gap implementation plan
|
||||
|
||||
The archetypes are covered; the gaps are inside them. Each item below is an independent commit.
|
||||
|
||||
### 6.1 NeoPixel Strip — new class `WS2812BStrip`
|
||||
|
||||
The most-used addressable form factor, absent entirely, and the one addition that also serves the
|
||||
existing guitar and amp audience.
|
||||
|
||||
1. New class in `displays` extending `AbstractMakerBoard`, GPL header from `HEADER.txt`, descriptor
|
||||
`name = "NeoPixel Strip (WS2812B)"`, `zOrder = COMPONENT`, `enableCache = true`.
|
||||
2. `Density` enum carrying LEDs per metre and the resulting pitch: `_30("30 LED/m")`,
|
||||
`_60("60 LED/m")`, `_144("144 LED/m")`. Strip width comes from the density (10 mm and 12 mm are
|
||||
the common tapes; 144/m is usually 12 mm).
|
||||
3. `LedCount` as an `@EditableProperty` integer, defaulting to 8, with a sane upper bound so a
|
||||
careless value cannot allocate an enormous control-point array. Four control points regardless of
|
||||
LED count: `DIN`, `+5V`, `GND` at one end and `DOUT` at the other — the LEDs are drawn, not
|
||||
wired, exactly as the ring does it.
|
||||
4. `getBodyShape()` is a rounded rectangle whose length is `ledCount * pitch`, so the part grows
|
||||
with the property.
|
||||
5. `draw()` fills the tape, then loops `drawAddressableLed` at the pitch, then `drawSolderPads` at
|
||||
each end. Silk via `getSilkPinLabel`.
|
||||
6. `drawIcon()` draws a short horizontal tape with four coloured dots, in the style the `Stick`
|
||||
icon already uses.
|
||||
|
||||
Tests: `assertBoardSize` at two densities, control-point count fixed at 4 across LED counts, a
|
||||
length-scales-with-count assertion, draw smoke at both densities. **~1 day including sourcing.**
|
||||
|
||||
### 6.2 `TFTDisplay` variants — `Controller` enum
|
||||
|
||||
The biggest structural gap, and it lands on top of the §5 item 3 footprint fix, so do it in the same
|
||||
pass rather than twice.
|
||||
|
||||
1. `Controller` enum: `ILI9341_2_8("2.8\" ILI9341 240x320 (Touch + SD)")`,
|
||||
`ST7735_1_8("1.8\" ST7735 128x160")`, `ST7789_1_54("1.54\" ST7789 240x240")`,
|
||||
`GC9A01_1_28("1.28\" GC9A01 240x240 Round")`. Default stays the ILI9341 so the icon is unchanged.
|
||||
2. Per-variant body size, pin count and pin array. The ILI9341 module is 14 pins with the touch and
|
||||
SD lines; the ST7735 and ST7789 boards are 8 pins (`GND VCC SCL SDA RES DC CS BLK`); the GC9A01
|
||||
is 7 or 8 depending on the board.
|
||||
3. `updateControlPoints()` sizes the array from the per-variant pin list, as `ESP32DevKit` does.
|
||||
4. `getBodyShape()` branches: rounded rectangle for three of them, a **circle with a tab** for the
|
||||
GC9A01, which is the one genuinely new outline.
|
||||
5. `draw()` branches on the screen aspect and on whether the SD-slot and touch hardware exist — only
|
||||
the ILI9341 has them.
|
||||
6. The round variant is worth the effort beyond its own popularity: it is what gauge, VU and clock
|
||||
projects reach for, which overlaps the existing audience.
|
||||
|
||||
Tests: per-variant `assertBoardSize` and pin-count, a round-outline assertion, draw smoke across all
|
||||
four. **~1.5-2 days, the largest item here.**
|
||||
|
||||
### 6.3 `OLEDDisplay` variants — `Version` enum
|
||||
|
||||
Do this in the same pass as the §5 item 5 glass fix.
|
||||
|
||||
1. `Version` enum: `SSD1306_0_96("0.96\" SSD1306 128x64")`, `SSD1306_0_91("0.91\" SSD1306 128x32")`,
|
||||
`SH1106_1_3("1.3\" SH1106 128x64")`. Default is the 0.96, so nothing about the current look
|
||||
changes.
|
||||
2. Per-variant board size — the 0.91" module is a small wide board, the 1.3" is larger than the
|
||||
0.96" — and per-variant glass size and position, taken from the active-area dimensions in the
|
||||
controller datasheets rather than guessed as a fraction of the board.
|
||||
3. Pin arrays are unchanged: the interface property already covers I2C 4-pin and SPI 7-pin, and it
|
||||
is orthogonal to the size. Both properties coexist; guard the combinations that are not sold
|
||||
(the 0.91" is I2C only) by branching in `draw()`, not by removing the property.
|
||||
4. `setVersion()` calls `updateControlPoints()` and `invalidateCache()`.
|
||||
|
||||
Tests: per-variant size, glass-aspect assertion, the interface property round-tripping against each
|
||||
version. **~0.5 day.**
|
||||
|
||||
### 6.4 `LEDMatrix8x8` cascade — `Modules` enum
|
||||
|
||||
1. `Modules` enum: `_1("Single 8x8 (32x32mm)")`, `_4("4-in-1 32x8 (128x32mm)")`. The 4-in-1 is
|
||||
probably the more commonly bought of the two.
|
||||
2. Board length scales with the module count; the input and output headers stay at the two ends.
|
||||
3. `draw()` loops the matrix block and its MAX7219 once per module, and draws a demo pattern across
|
||||
the full width rather than repeating an 8x8 pattern four times.
|
||||
4. Rename nothing: the class name `LEDMatrix8x8` is now slightly narrow, but it has to stay, and
|
||||
the descriptor `name` can be widened to "LED Matrix (MAX7219)" freely because no file
|
||||
references it yet (D6).
|
||||
5. While in the file, replace the hardcoded `-44 mm` header offset with a derived value.
|
||||
|
||||
Tests: per-variant size, header positions at both ends in both variants, draw smoke.
|
||||
**~0.5-1 day.**
|
||||
|
||||
### 6.5 Nokia 5110 LCD — new class `Nokia5110LCD`
|
||||
|
||||
Still in every starter kit, a distinct 8-pin footprint, and simple geometry: a board with a large
|
||||
glass area and a single 1x8 header. PCD8544 controller, 84x48 pixels, `RST CE DC DIN CLK VCC BL GND`.
|
||||
One class, no variants. **~0.5 day.**
|
||||
|
||||
### 6.6 LED Bar Graph — new class, base class to be decided
|
||||
|
||||
Ten segments in a 20-pin DIP outline. Nothing in the library covers it: `LED`, `LEDSymbol` and
|
||||
`PilotLampHolder` are the closest, and none is a segmented bar. It is the cheapest item here and the
|
||||
only one that pays off entirely within the **existing** audience, for VU and level meters.
|
||||
|
||||
The base class is an open question (§11.3). `AbstractMakerBoard` is the wrong fit — this is not a
|
||||
board — and the established shape for a dual-row DIP part is `DIL_IC`, which extends
|
||||
`AbstractLabeledComponent`, carries a `PinCount` enum and points at a dedicated
|
||||
`DIL_ICTransformer`. Recommendation: follow `DIL_IC`, with a fixed 20-pin geometry rather than a pin
|
||||
count property, a `Segments` enum for the 10-segment and 8-segment variants, and a `Color` property
|
||||
for the lit colour. Category `Displays & Outputs` even though the neighbours in shape live in
|
||||
`Semiconductors`, because that is where someone will look for it.
|
||||
|
||||
**~0.5-1 day once the base class is settled.**
|
||||
|
||||
### 6.7 Cheap additions
|
||||
|
||||
- **`WS2812BRing`**: append `_8_LED` and a `_7_LED` Jewel constant to `RingSize` with their OD/ID.
|
||||
Two lines plus sourcing. **~1 h.**
|
||||
- **NeoPixel panel 8x8** as its own class, reusing `drawAddressableLed` and the ring's pad handling.
|
||||
Worth doing only after §6.1, whose helper it depends on. **~0.5 day.**
|
||||
|
||||
### 6.8 Deliberately not in this slice
|
||||
|
||||
E-paper (1.54" / 2.9" SSD1680) — growing but still niche, and the flexible-cable mounting is real
|
||||
new drawing work. Also: 14- and 16-segment alphanumeric, HT16K33 backpacks, 1.2" and 2.3" large
|
||||
digits, 3.5" ILI9488, VFD, DotStar and APA102, and the RGB-backlight 1602.
|
||||
|
||||
## 7. Testing
|
||||
|
||||
Per-class tests under `diylc-library/src/test/java/org/diylc/components/displays/`, named
|
||||
`*Test.java`, GPL header like any other file, following the `micro` pattern and using the promoted
|
||||
`MakerBoardTestSupport`:
|
||||
|
||||
- `testControlPointCountAndNames()` — count, no empty names, spot checks, and for
|
||||
`WS2812BStick` an explicit **no duplicate node names** assertion given §3.1.
|
||||
- `testDimensionsAndGeometry()` — `assertBoardSize` against the sourced dimensions, header edge and
|
||||
pitch via `assertRow`.
|
||||
- `testVariantProperty()` — default, labels, round-trip through the setter, for every class that
|
||||
gains an enum in §6.
|
||||
- a drawing smoke test over every variant in both header states and all three draw modes.
|
||||
|
||||
`MakerComponentsTest` keeps its generic coverage; move the seven (then nine) classes into
|
||||
`releasedMakerComponentClasses` as they ship, and add the new classes to the list as they are
|
||||
written.
|
||||
|
||||
## 8. Regression suite and release
|
||||
|
||||
**There is no rendering-regression coverage for any maker component at all.** No `.diy` file among
|
||||
the 1418 in `diylc-regression-data` references anything in `org.diylc.components.micro`, so batch 1
|
||||
shipped with none either. This slice should add a small number of sample projects — one per display
|
||||
class with a couple of variants each, ideally wired to a controller from batch 1 — and regenerate
|
||||
the PNG and netlist baselines. That is the only mechanism that would catch a silent rendering or
|
||||
node-naming change later, and the netlist baseline is what would have caught §3.1's duplicate
|
||||
grounds.
|
||||
|
||||
Release notes go in a **new `6.7.0` block** in `diylc-core/src/main/resources/update.xml`. 6.6.0 is
|
||||
already dated and released, and released blocks are not edited. One `NEW_FEATURE` entry per palette
|
||||
entry, following the 6.5.0 wording for the controllers ("Arduino Nano - ATmega328, 33 BLE, ...":
|
||||
part name, then the variant list).
|
||||
|
||||
## 9. Sequencing
|
||||
|
||||
1. ~~**§5 items 1 and 2**~~ — **done**: test support promotion and the shared LED helper. Everything
|
||||
else builds on them.
|
||||
2. **§5 items 3-9** — the prep pass, finishing with the sweep in item 9.
|
||||
3. **§5 item 10 and §6.2-6.4** — sourcing, folded into the variant work for the three classes that
|
||||
gain enums, since both touch the same constants.
|
||||
4. **§6.1, §6.5, §6.6, §6.7** — the new classes, each an independent commit.
|
||||
5. **§7** tests alongside each of the above, not after.
|
||||
6. **§8** regression samples and the `update.xml` block, last.
|
||||
|
||||
Nine palette entries at the end of it: the seven existing, plus the strip and the bar graph, plus
|
||||
the Nokia and the panel if appetite holds.
|
||||
|
||||
## 10. Later slices, and why they are later
|
||||
|
||||
**`sensors` (9 drafts).** The archetype argument fails here: there are hundreds of sensor breakouts
|
||||
and no canonical member of most functions, so nine drafted parts are a sample rather than coverage.
|
||||
Four of them — HC-SR04 ultrasonic, DHT11/22, PIR HC-SR501, MPU6050 — are genuinely the unrivalled
|
||||
answer in their function and appear in every beginner kit; the other five (LDR, soil moisture,
|
||||
TCRT5000, IR receiver KY-022, BME280) are commodity KY-style three-pin boards, visually near
|
||||
identical, carrying most of the per-part sourcing cost for the least distinctiveness. When this
|
||||
slice happens, ship the four and leave the rest. Do **not** collapse the commodity tail into a
|
||||
parameterised "three-pin sensor module": that is the shape of the generic configurable board that
|
||||
was already built and reverted, and it fails for the same reason — people search the palette for a
|
||||
product name.
|
||||
|
||||
**`modules` (21 drafts).** The largest and least homogeneous batch — motor drivers, buck and boost
|
||||
converters, chargers, radios, RTCs — and the worst geometry drift in the tree (`ULN2003Driver` has
|
||||
24 raw pixel offsets, `PCA9685ServoDriver` 22, `L298NMotorDriver` 18). Worth splitting by function
|
||||
rather than shipping as one category. One known overlap to settle first: the drafted
|
||||
`ActiveBuzzerModule` (KY-012) against the existing `misc/Buzzer` ("PCB Buzzer, active or passive").
|
||||
|
||||
**`robotics` (4 drafts).** Blocked on an architectural decision, not on part selection.
|
||||
`AbstractMotor` is a parallel fork of `AbstractMakerBoard`, re-implementing `rotatePoints`,
|
||||
`drawPins`, `getFinalBorderColor` and `getCachingBounds` plus its own copies of `PIN_COLOR`,
|
||||
`PIN_SIZE` and the silk fonts. Four components do not justify a second hierarchy; either lift the
|
||||
shared behaviour into a common base or take the duplication deliberately. Either way it should not
|
||||
gate the displays.
|
||||
|
||||
## 11. Open decisions
|
||||
|
||||
1. **BOM treatment of variants** — **decided; the board roadmap's §8.2 is settled by it too.** Every
|
||||
maker board uses `bomPolicy = SHOW_ONLY_TYPE_NAME` and returns `null` from `getValue()`, which
|
||||
left the BOM's value column empty. That was not merely a missing label: `BomMaker` groups rows on
|
||||
`typeName + "|" + value`, so every variant of a class **collapsed into one row** — two Unos of
|
||||
different versions printed as a single line of quantity two, and the BOM could not say which part
|
||||
to buy.
|
||||
|
||||
A new `BomPolicy` constant was considered and rejected. The policy only chooses what goes in the
|
||||
*name* field (`SHOW_ALL_NAMES ? name : typeName`); it has no bearing on `value` or on the grouping
|
||||
key, folding the variant into the name column would break the two-column convention every other
|
||||
component follows (`R1, R2` / `10k`), and filling it would mean `diylc-core` reaching into
|
||||
concrete component types.
|
||||
|
||||
Instead `AbstractMakerBoard` grows a `protected String getVariantLabel()` hook and overrides
|
||||
`getValueForDisplay()` to return it. The variant lands in the value column, which is already
|
||||
rendered, already exported and already part of the grouping key, so the rows split correctly with
|
||||
**no change to `BomMaker` at all**. Eleven classes override the hook; the six boards sold in a
|
||||
single form inherit the `null` default and keep an empty value.
|
||||
|
||||
One consequence to remember: `getValueForDisplay()` also feeds component search in `Presenter` and
|
||||
the node labels in `AbstractNetlistAnalyzer`, so netlist output gains the variant for any project
|
||||
containing a maker board. No regression sample contains one today, which is precisely why this is
|
||||
cheaper to do now than after §8's samples are written.
|
||||
2. **A uniform property set** — the board roadmap's §8.3. This slice adds `Version`-style enums to
|
||||
three more classes; deciding now whether a "Show Pin Labels" toggle and a consistent `Headers`
|
||||
property belong on every maker component avoids retrofitting nine more classes later.
|
||||
3. **The bar graph's base class** (§6.6) — follow `DIL_IC` on `AbstractLabeledComponent` with a
|
||||
transformer, or extend `AbstractMakerBoard` for consistency with its category neighbours. The
|
||||
recommendation is `DIL_IC`, because the part is a DIP package and rotation for DIP parts already
|
||||
has a transformer.
|
||||
4. **Whether `LedCount` on the strip should be a free integer or an enum** (§6.1). A free integer
|
||||
matches how someone thinks about a tape they will cut; an enum keeps the property editor
|
||||
predictable and bounds the control-point maths. The plan above assumes a bounded integer.
|
||||
5. **The TFT's mounting holes** — **decided, from the vendor drawing.** Four holes: all of them
|
||||
3 mm in from the side edges, the pair nearest the header 6.92 mm down from that edge so they
|
||||
clear the pin row, the lower pair 3 mm up from the bottom edge. The glass is centred between the
|
||||
two hole rows rather than measured from an edge, which is what reconciles a 69.1 mm glass with
|
||||
holes only 3 mm from the bottom: its midpoint is 44.96 mm from the top, so it spans 10.41 mm to
|
||||
79.51 mm and clears both pairs. Both are derived in `draw()` from the hole constants, so moving
|
||||
a hole moves the glass with it.
|
||||
6. **Where the Stick's pad labels go** — **decided: nowhere.** The front face carries no
|
||||
silkscreen, matching the board, which prints its pad names on the back; node-name tooltips and
|
||||
the netlist already carry the names. With the labels gone the LED row takes the real layout —
|
||||
eight 5 mm packages centred on the 51.1 mm board — and the field inset is derived from that
|
||||
rather than tuned to leave text room, so `LED_FIELD_INSET` is gone.
|
||||
7. **i18n** — component display names and enum labels are plain strings, absent from
|
||||
`diylc-swing/src/main/resources/lang/*.txt`, as they are for the shipped boards. No translation
|
||||
work is required here; noted for a future pass.
|
||||
8. **`CharacterLCD`'s geometry cannot be sourced consistently** — which is why item 6 is done for
|
||||
every other file and blocked for this one. "1602" names a character format, not a mechanical
|
||||
part, and vendors ship several outlines under it: the enum encodes an 80 × 36 mm module, while
|
||||
Raystar's RC1602A is 84 × 44 mm. The display window is worse, because sources quote different
|
||||
features under similar names — 64.5 × 16 mm and 66.0 × 16 mm as "viewing area" against
|
||||
56.2 × 11.5 mm as "active area" for the 1602, and 84 × 31 mm against 76 × 26 mm for the 2004.
|
||||
**No mounting-hole positions were found for any of them.** The render confirms this is a visible
|
||||
defect rather than a tidiness one: the bezel currently occupies roughly 70% of the board height
|
||||
when a 16 mm window on a 36 mm board should be under half. What would unblock it is picking one
|
||||
specific part — as the TFT slice settled on the MSP2807 — and taking the outline, window and
|
||||
hole positions from that one drawing, rather than assembling them from whichever vendor answers
|
||||
a search first.
|
||||
9. **How many pads does a NeoPixel Ring have?** — **and this one has a deadline.** The component
|
||||
models four (`DIN`, `+5V`, `GND`, `DOUT`), and the NeoPixel guide confirms those labels are
|
||||
right. But the 16-LED ring's own changelog records that Adafruit "added an extra ground and
|
||||
power breakout" in July 2014, so the shipping part almost certainly has six. Three Adafruit
|
||||
sources — the product pages, the rings guide and the basic-connections guide — all decline to
|
||||
state the count. **Pad count is control-point count.** Under D6 that is free to change today,
|
||||
because nothing shipped references these classes; once 6.7.0 is released it becomes a
|
||||
file-format migration. So this is the one open item that gets materially more expensive by
|
||||
waiting, and the cheapest way to close it is a look at a physical ring or its EagleCAD files.
|
||||
10. **`SevenSegmentDisplay`'s pin arrays are unverified, and one of them is suspect.** The 10-pin
|
||||
structure is corroborated — a 0.56" single digit such as the 5161AS has ten pins, two of them
|
||||
common — but no source consulted would give an *ordered* pin list, so `PIN_NAMES_1DIGIT` and
|
||||
`PIN_NAMES_4DIGIT` remain unchecked. The specific worry is the 12-pin array: it follows the
|
||||
3641AS, which is a **0.36-inch** part, while `DIGIT_HEIGHT` declares 14.2 mm, i.e. 0.56". The
|
||||
0.56" four-digit equivalent is the 5641AS, and if its pinout differs then the component pairs
|
||||
one part's pins with another part's dimensions. Four attempts failed to settle it: both xlitx
|
||||
HTML pages carry only electro-optical tables, and both linked datasheets are image-encoded PDFs
|
||||
of the kind that also defeated the MSP2807 manual. **This carries the same D6 deadline as §11.9
|
||||
and is worse in consequence** — a wrong dimension is a wrong picture, a wrong pin array is a
|
||||
wrong netlist, and it is silent. Closing it needs a readable datasheet or a part in hand.
|
||||
|
||||
Note that replacing the digit glyph with the standard unit-grid construction did **not** touch
|
||||
this. The glyph governs how a lit segment is shaped; the pin arrays govern which physical pin
|
||||
that segment is wired to. The first is now on a documented footing and the second is not, and
|
||||
it would be easy to read the file and assume otherwise.
|
||||
|
||||
The enum split does not touch it either, but it does improve the odds of settling it. Both bare
|
||||
four-digit packages share `PIN_NAMES_4DIGIT`, on the assumption that a family differs in size
|
||||
rather than in pin function — so **one datasheet now corrects both variants at once**. If that
|
||||
assumption turns out to be wrong, the arrays have to separate, and that is itself worth knowing.
|
||||
The suspicion recorded above, that the array follows the 0.36" part while the dimensions
|
||||
described the 0.56" one, is precisely what having both variants is meant to resolve.
|
||||
11. **What the enum split left unsourced.** The split itself is sound; what follows is geometry that
|
||||
should not be mistaken for measurement.
|
||||
|
||||
*The 0.36" variant is now mostly sourced.* Its body (30 × 14 mm), digit pitch (7.5 mm) and row
|
||||
spacing (0.4", 10.16 mm) all came from the maintainer, and its digit height is definitional,
|
||||
because 9.14 mm is what 0.36 inches means. **One figure remains invented: the digit width
|
||||
(5.2 mm)**, derived by applying the 0.56" part's 8.1 : 14.2 ratio to a 9.14 mm digit — and those
|
||||
0.56" figures are themselves unverified, so it rests on an unchecked foundation. It is at least
|
||||
not contradicted by the rest: four digits on 7.5 mm centres span 22.5 mm, which with one digit
|
||||
width leaves about 1.15 mm clear at each end of a 30 mm body. Worth replacing with a measurement
|
||||
rather than leaving as an inference.
|
||||
|
||||
For the record, the values this replaced were mine and were fitted rather than measured — a
|
||||
30.2 × 14.0 body, an 8.0 mm pitch, and a 12.7 mm row spacing chosen only so the rows would sit
|
||||
inside the body after the shared 0.6" value left them hanging past its edges.
|
||||
|
||||
*The TM1637's header position is fixed.* Its single row of four pins had been centred on the
|
||||
body, putting it over the display bezel; it now sits 2.5 mm above the bottom edge on bare board,
|
||||
via `HEADER_EDGE_OFFSET`. Worth knowing that many real modules carry that header on the **left
|
||||
edge, vertical**, which would mean laying the single-row case out vertically rather than
|
||||
horizontally — a larger change that was considered and not made. The centring predated the
|
||||
split; what made it visible was rendering each variant rather than only the default one.
|
||||
Reference in New Issue
Block a user