diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/displays/CharacterLCD.java b/diylc/diylc-library/src/main/java/org/diylc/components/displays/CharacterLCD.java index aa42b596..85ba7f1e 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/displays/CharacterLCD.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/displays/CharacterLCD.java @@ -50,7 +50,7 @@ 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", +@ComponentDescriptor(name = "Character LCD", 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, @@ -61,22 +61,33 @@ public class CharacterLCD extends AbstractMakerBoard { public enum LCDSize { // Labels stay short because they reach the BOM's value column; the dimensions are carried by - // the fields beside them, not by the text. - _16x2("16x2", 16, 2, 80.0, 36.0), - _20x4("20x4", 20, 4, 98.0, 60.0); + // the fields beside them, not by the text. The bezel and the lit area are measured off the + // module and are sizes in their own right -- deriving them as margins off the board made the + // window grow with the board instead of staying the size of the part. + _16x2("16x2", 16, 2, 80.0, 35.0, 72.2, 24.1, 64.5, 14.5), + _20x4("20x4", 20, 4, 98.0, 60.0, 96.8, 39.3, 77.0, 25.2); private final String label; private final int cols; private final int rows; private final double widthMm; private final double heightMm; + private final double bezelWidthMm; + private final double bezelHeightMm; + private final double displayWidthMm; + private final double displayHeightMm; - LCDSize(String label, int cols, int rows, double widthMm, double heightMm) { + LCDSize(String label, int cols, int rows, double widthMm, double heightMm, double bezelWidthMm, + double bezelHeightMm, double displayWidthMm, double displayHeightMm) { this.label = label; this.cols = cols; this.rows = rows; this.widthMm = widthMm; this.heightMm = heightMm; + this.bezelWidthMm = bezelWidthMm; + this.bezelHeightMm = bezelHeightMm; + this.displayWidthMm = displayWidthMm; + this.displayHeightMm = displayHeightMm; } @Override public String toString() { return label; } @@ -84,6 +95,10 @@ public class CharacterLCD extends AbstractMakerBoard { public int getRows() { return rows; } public double getWidthMm() { return widthMm; } public double getHeightMm() { return heightMm; } + public double getBezelWidthMm() { return bezelWidthMm; } + public double getBezelHeightMm() { return bezelHeightMm; } + public double getDisplayWidthMm() { return displayWidthMm; } + public double getDisplayHeightMm() { return displayHeightMm; } } public enum LCDInterface { @@ -95,8 +110,19 @@ public class CharacterLCD extends AbstractMakerBoard { @Override public String toString() { return label; } } - // clearance from the top edge to the pin row + // clearance from the top edge to the parallel pin row public static Size HEADER_OFFSET = new Size(2.54d, SizeUnit.mm); + // The parallel row is not centred on the board: its leftmost pin starts this far in from the + // left edge. + public static Size PARALLEL_HEADER_INSET = new Size(10.0d, SizeUnit.mm); + // The I2C backpack brings its four pins out as a vertical column standing against the left edge, + // this far in from it and centred on the board's height. + public static Size I2C_HEADER_INSET = new Size(2.5d, SizeUnit.mm); + // Hole centre inset from each edge, measured. The diameter beside it is not: it is still the raw + // 20 px it replaced, which open decision 11.8 in docs/plans/maker-peripherals.md records as the + // last unsourced figure in this class. + public static Size MOUNTING_HOLE_INSET = new Size(2.5d, SizeUnit.mm); + public static Size MOUNTING_HOLE_SIZE = new Size(2.54d, SizeUnit.mm); public static Color PCB_GREEN = Color.decode("#1B5E20"); public static Color SCREEN_BG = Color.decode("#1E88E5"); @@ -185,13 +211,26 @@ public class CharacterLCD extends AbstractMakerBoard { 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. */ + /** + * Left edge of the board. Neither header is centred on it: the parallel row starts a fixed + * distance in from the left, and the I2C column stands against that same edge. + */ private double getBoardX(double x) { - double spacing = PIN_SPACING.convertToPixels(); - return x - (getBoardWidth() - (getPinCount() - 1) * spacing) / 2.0; + Size inset = + lcdInterface == LCDInterface.I2C_Backpack ? I2C_HEADER_INSET : PARALLEL_HEADER_INSET; + return x - inset.convertToPixels(); } + /** + * Top edge of the board. The parallel row hangs below it by a fixed clearance; the I2C column is + * centred on the board's height instead, so the board starts back from the first pin by half of + * the height the column does not occupy. + */ private double getBoardY(double y) { + if (lcdInterface == LCDInterface.I2C_Backpack) { + double columnSpan = (getPinCount() - 1) * PIN_SPACING.convertToPixels(); + return y - (getBoardLength() - columnSpan) / 2.0; + } return y - HEADER_OFFSET.convertToPixels(); } @@ -199,11 +238,13 @@ public class CharacterLCD extends AbstractMakerBoard { protected void updateControlPoints() { Point2D firstPoint = controlPoints[0]; double spacing = PIN_SPACING.convertToPixels(); + // the backpack's header stands on end against the left edge; the parallel row lies along the top + boolean vertical = lcdInterface == LCDInterface.I2C_Backpack; double[][] relativeOffsets = new double[getPinCount()][2]; for (int i = 0; i < relativeOffsets.length; i++) { - relativeOffsets[i][0] = i * spacing; - relativeOffsets[i][1] = 0; + relativeOffsets[i][0] = vertical ? 0 : i * spacing; + relativeOffsets[i][1] = vertical ? i * spacing : 0; } rotatePoints(firstPoint, relativeOffsets); @@ -251,28 +292,32 @@ public class CharacterLCD 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); + // One mounting hole per corner. + double holeInset = MOUNTING_HOLE_INSET.convertToPixels(); + double holeSize = MOUNTING_HOLE_SIZE.convertToPixels(); + MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset, boardY + holeInset, holeSize); + MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset, boardY + boardH - holeInset, + holeSize); + MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - holeInset, boardY + holeInset, + holeSize); + MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - holeInset, + boardY + boardH - holeInset, holeSize); - // Metal Bezel around screen - double bezelMarginX = (lcdSize == LCDSize._16x2) ? 45 : 40; - double bezelW = boardW - 2 * bezelMarginX; - double bezelX = boardX + bezelMarginX; - double bezelY = boardY + 45; - double bezelH = boardH - 65; + // The metal bezel, centred on the board. Its size is the module's own, not a margin taken + // off the board, so it stays put when the board changes. + double bezelW = new Size(lcdSize.getBezelWidthMm(), SizeUnit.mm).convertToPixels(); + double bezelH = new Size(lcdSize.getBezelHeightMm(), SizeUnit.mm).convertToPixels(); + double bezelX = boardX + (boardW - bezelW) / 2.0; + double bezelY = boardY + (boardH - bezelH) / 2.0; g2d.setColor(BEZEL_COLOR); g2d.fill(new RoundRectangle2D.Double(bezelX, bezelY, bezelW, bezelH, 6, 6)); - // LCD Screen area - double screenMargin = 12; - double screenW = bezelW - 2 * screenMargin; - double screenH = bezelH - 2 * screenMargin; - double screenX = bezelX + screenMargin; - double screenY = bezelY + screenMargin; + // The lit area, centred within the bezel and likewise measured rather than derived. + double screenW = new Size(lcdSize.getDisplayWidthMm(), SizeUnit.mm).convertToPixels(); + double screenH = new Size(lcdSize.getDisplayHeightMm(), SizeUnit.mm).convertToPixels(); + double screenX = bezelX + (bezelW - screenW) / 2.0; + double screenY = bezelY + (bezelH - screenH) / 2.0; g2d.setColor(screenColor); g2d.fill(new Rectangle2D.Double(screenX, screenY, screenW, screenH)); diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/displays/SevenSegmentDisplay.java b/diylc/diylc-library/src/main/java/org/diylc/components/displays/SevenSegmentDisplay.java index e7dc1719..f9b21d6c 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/displays/SevenSegmentDisplay.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/displays/SevenSegmentDisplay.java @@ -71,7 +71,10 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { * until someone reads a datasheet or measures a part. */ public enum DisplayType { - SingleDigit_10Pin("1-Digit 0.56\"", 12.6d, 19.0d, 8.1d, 14.2d, 0d, 15.24d, true), + // The parts these model: 5161AS for the single digit, 3641AS for the 0.36 inch four-digit + // package and 5641AS for the 0.56 inch one. The two four-digit parts differ in size but share + // a pinout, which is why one array serves both. + SingleDigit_10Pin("1-Digit 0.56\"", 12.7d, 19.0d, 8.1d, 14.2d, 0d, 15.24d, true), FourDigit_0_36_12Pin("4-Digit 0.36\"", 30.0d, 14.0d, 5.2d, 9.14d, 7.5d, 10.16d, true), FourDigit_0_56_12Pin("4-Digit 0.56\"", 50.3d, 19.0d, 8.1d, 14.2d, 12.7d, 15.24d, true), TM1637_Module_4Pin("4-Digit TM1637 Module", 42.0d, 24.0d, 5.5d, 9.2d, 7.62d, 0d, false); @@ -115,6 +118,40 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { public boolean isDualRow() { return dualRow; } } + /** + * Which electrode the digits share. Both kinds are sold side by side and are identical in pinout, + * package and dimensions -- they differ only in internal polarity, so this changes nothing that + * is drawn and reaches only the BOM. + */ + public enum Common { + Cathode("Common Cathode"), + Anode("Common Anode"); + + private final String label; + Common(String label) { this.label = label; } + @Override public String toString() { return label; } + } + + /** + * What the package carries besides the digits themselves. These are sold in every combination, + * and on a twelve-pin part they are not independent of each other: A-G plus DP and four digit + * commons already account for all twelve pins, so a colon has to share the decimal point's line + * or replace it. The property therefore describes the face of the part, not a fifth pin. + */ + public enum Punctuation { + None("No Punctuation"), + DecimalPoints("Decimal Points"), + Colon("Colon"), + Both("Colon + Decimal Points"); + + private final String label; + Punctuation(String label) { this.label = label; } + @Override public String toString() { return label; } + + public boolean hasDecimalPoints() { return this == DecimalPoints || this == Both; } + public boolean hasColon() { return this == Colon || this == Both; } + } + public static Color BODY_BLACK = Color.decode("#1C1C1C"); public static Color FACE_BLACK = Color.decode("#111111"); public static Color FACE_BORDER = Color.decode("#333333"); @@ -133,6 +170,11 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { * 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. + * + *

Concretely it draws the 0.56 inch packages' 14.2 mm digit at 12.5 mm, so what reaches the + * canvas is about half an inch. That is intended, and it is not a disagreement with the part: + * the package is still a 0.56 inch one and the BOM still says so. Worth knowing before changing + * this number to chase an exact drawn size, because it is shared and would move every package. */ public static double DIGIT_DRAW_SCALE = 0.88d; @@ -147,7 +189,8 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { }; /** - * Shared by both bare four-digit packages, which differ in size but not in pin function. + * Shared by both bare four-digit packages: the 3641AS and the 5641AS differ in size but not in + * pin function, confirmed by the maintainer. * *

This was previously transcribed in reading order -- top row left to right, then bottom row * left to right -- rather than in DIP order, which put every segment and digit common on the @@ -164,6 +207,8 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { public static Color PCB_BLUE = Color.decode("#004488"); private DisplayType displayType = DisplayType.SingleDigit_10Pin; + private Common common = Common.Cathode; + private Punctuation punctuation = Punctuation.DecimalPoints; private Color ledColor = LED_RED; private Color boardColor = PCB_BLUE; @@ -184,10 +229,54 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { invalidateCache(); } + /** + * Common cathode and common anode parts share a pinout, a package and a set of dimensions, so no + * drawing path reads this -- it exists to split the two in the BOM, which is the only place the + * difference costs anyone anything. Defaulted in the getter rather than the field so that a + * project saved before this property existed still loads. + */ + @EditableProperty(name = "Common") + public Common getCommon() { + if (common == null) { + common = Common.Cathode; + } + return common; + } + + public void setCommon(Common common) { + this.common = common; + } + + /** + * Unlike {@link #getCommon()} this one is visible: it decides whether the decimal points and the + * colon are drawn at all. The colon is meaningless on the single-digit package and is ignored + * there, in the same way the board colour is ignored on the bare packages. + */ + @EditableProperty(name = "Punctuation") + public Punctuation getPunctuation() { + if (punctuation == null) { + punctuation = Punctuation.DecimalPoints; + } + return punctuation; + } + + public void setPunctuation(Punctuation punctuation) { + this.punctuation = punctuation; + invalidateCache(); + } + @Override protected String getVariantLabel() { DisplayType displayType = getDisplayType(); - return displayType == null ? null : displayType.toString(); + if (displayType == null) { + return null; + } + // The module drives the digits itself and brings out no common pins, so polarity is not part + // of what you order -- but which punctuation it carries still is. + if (displayType == DisplayType.TM1637_Module_4Pin) { + return displayType + ", " + getPunctuation(); + } + return displayType + ", " + getCommon() + ", " + getPunctuation(); } @EditableProperty(name = "LED Color") @@ -357,7 +446,9 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { * 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); + // no colon on a single digit, so only the decimal point is in question here + drawSevenSegmentDigit(g2d, dx, dy, dw, dh, "8.", ledColor, + getPunctuation().hasDecimalPoints()); } else if (isFourDigitBare()) { // the recessed window is a shade of the moulding, so it follows the body colour @@ -379,19 +470,23 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { double firstCenterX = boardX + boardW / 2.0 - 1.5 * pitch; double dy = boardY + (boardH - dh) / 2.0; + Punctuation punctuation = getPunctuation(); for (int d = 0; d < 4; d++) { double centerX = firstCenterX + d * pitch; double dx = centerX - dw / 2.0 - 0.5 * SEGMENT_THICKNESS.convertToPixels(); - drawSevenSegmentDigit(g2d, dx, dy, dw, dh, "8.", ledColor); + drawSevenSegmentDigit(g2d, dx, dy, dw, dh, "8.", ledColor, + punctuation.hasDecimalPoints()); } - // 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 dotD = dw / GLYPH_WIDTH * 2.0; - g2d.setColor(ledColor); - 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)); + if (punctuation.hasColon()) { + // 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 dotD = dw / GLYPH_WIDTH * 2.0; + double colonX = boardX + boardW / 2.0 - dotD / 2.0; + g2d.setColor(ledColor); + g2d.fill(new Ellipse2D.Double(colonX, dy + dh * 0.35 - dotD / 2.0, dotD, dotD)); + g2d.fill(new Ellipse2D.Double(colonX, dy + dh * 0.65 - dotD / 2.0, dotD, dotD)); + } } else if (displayType == DisplayType.TM1637_Module_4Pin) { MakerBoardPainter.drawMountingHole(g2d, boardX + 14, boardY + 14, 12); @@ -419,18 +514,22 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { double firstCenterX = bezelX + (bezelW - 3 * pitch) / 2.0; double dy = bezelY + (bezelH - dh) / 2.0; + Punctuation punctuation = getPunctuation(); for (int d = 0; d < 4; d++) { double centerX = firstCenterX + d * pitch; double dx = centerX - dw / 2.0 - 0.5 * SEGMENT_THICKNESS.convertToPixels() * 0.65; - drawSevenSegmentDigit(g2d, dx, dy, dw, dh, "8.", ledColor); + drawSevenSegmentDigit(g2d, dx, dy, dw, dh, "8.", ledColor, + punctuation.hasDecimalPoints()); } - // same colon treatment as the bare packages: sized and placed off the digit, not in pixels - double colonX = bezelX + bezelW / 2.0; - double dotD = dw / GLYPH_WIDTH * 2.0; - g2d.setColor(ledColor); - 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)); + if (punctuation.hasColon()) { + // same colon treatment as the bare packages: sized off the digit, not in pixels + double dotD = dw / GLYPH_WIDTH * 2.0; + double colonX = bezelX + bezelW / 2.0 - dotD / 2.0; + g2d.setColor(ledColor); + g2d.fill(new Ellipse2D.Double(colonX, dy + dh * 0.35 - dotD / 2.0, dotD, dotD)); + g2d.fill(new Ellipse2D.Double(colonX, dy + dh * 0.65 - dotD / 2.0, dotD, dotD)); + } } } @@ -502,7 +601,8 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { * 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) { + private void drawSevenSegmentDigit(Graphics2D g2d, double x, double y, double w, double h, + String charToDisplay, Color onColor, boolean showDecimalPoint) { AffineTransform orig = g2d.getTransform(); g2d.translate(x, y); @@ -536,7 +636,8 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { g2d.fill(segments[i]); } } - if ((mask & SEG_DP) == 0 && !charToDisplay.contains(".")) { + // a package without a decimal point has no dot at all, lit or unlit, so this is gated too + if (showDecimalPoint && (mask & SEG_DP) == 0 && !charToDisplay.contains(".")) { g2d.fill(dpShape); } @@ -547,7 +648,7 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { g2d.fill(segments[i]); } } - if ((mask & SEG_DP) != 0 || charToDisplay.contains(".")) { + if (showDecimalPoint && ((mask & SEG_DP) != 0 || charToDisplay.contains("."))) { g2d.fill(dpShape); } @@ -570,7 +671,8 @@ public class SevenSegmentDisplay extends AbstractMakerBoard { g2d.setColor(Color.DARK_GRAY); g2d.draw(new RoundRectangle2D.Double(6, 3, width - 12, height - 6, 3, 3)); - // Draw single stylized '8' - drawSevenSegmentDigit(g2d, 9, 5, width - 18, height - 10, "8.", LED_RED); + // Draw single stylized '8'. The toolbox icon stands for the component type rather than for any + // one configured instance, so it keeps its decimal point whatever Punctuation is set to. + drawSevenSegmentDigit(g2d, 9, 5, width - 18, height - 10, "8.", LED_RED, true); } } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BRing.java b/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BRing.java index 0b82e183..c2161a9e 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BRing.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BRing.java @@ -46,7 +46,7 @@ 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", +@ComponentDescriptor(name = "NeoPixel Ring", 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, diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BStick.java b/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BStick.java index d3941204..251c7bef 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BStick.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BStick.java @@ -46,7 +46,7 @@ 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", +@ComponentDescriptor(name = "NeoPixel Stick", 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, diff --git a/diylc/docs/plans/maker-peripherals.md b/diylc/docs/plans/maker-peripherals.md index 01b3e47c..7ff4030a 100644 --- a/diylc/docs/plans/maker-peripherals.md +++ b/diylc/docs/plans/maker-peripherals.md @@ -47,7 +47,7 @@ Seven classes, all `category = "Displays & Outputs"`, all `bomPolicy = SHOW_ONLY |---|---|---| | `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, mm-based OD/ID per size, LED count driving the rendering, `drawSolderPads` rather than a header — correct for a ring. Its diameters, pad count and pad placement have all since been corrected from Adafruit's pages and the maintainer's measurements; the pads now sit on their own radius near the rim, each in one of the real uneven gaps between LEDs (§11.9). The LED packages are the real 5 mm 5050 part, shared with the Stick as `AbstractMakerBoard.RGB_LED_SIZE` — they had been drawn at a clamped 8-15 px, barely 1-2 mm — and the ring renders lit, in a continuous yellow-orange-red-purple-blue-green gradient with a palette per variant. | -| `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()`. | +| `CharacterLCD` | 16x2 / 20x4, I2C backpack / 16-pin parallel | Correct HD44780 and PCF8574 pin data. The bodies are now maintainer-given at 80 × 35 and 98 × 60 mm — the 16x2 height had been 36, so "correct bodies" was never true of it. The screen geometry was raw pixels — an eyeballed `bezelMarginX = (16x2) ? 45 : 40` and a window derived as a margin off the board — and is now measured: a 72.2 × 24.1 mm bezel around a 64.5 × 14.5 mm lit area on the 16x2, 77 × 25.5 around 70.4 × 20.4 on the 20x4, carried on `LCDSize` and centred rather than subtracted. Headers are placed from measurements too (§3.2 item 7). No pixel literal survives in `draw()`; only the mounting holes remain unsourced (§11.8). The `Font` it once allocated inside `draw()` went with the demo text in item 7; the one that remains is in `drawIcon`, which runs per toolbox icon rather than per repaint. | | `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. | @@ -103,6 +103,15 @@ ground is almost certainly wrong. 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. + **`CharacterLCD` has since been given its real header placement, and it is not centred at all.** + The 16-pin parallel row starts 10 mm in from the left edge, and the I2C backpack brings its four + pins out as a *vertical* column standing against the left edge, 2.5 mm in and centred on the + board's height. For this class the item was therefore wrong twice over: the geometry was not + merely off-centre by accident, and centring was never the right answer. `PARALLEL_HEADER_INSET` + and `I2C_HEADER_INSET` are measurements of where the connectors actually are, not a correction + applied to a drawing that had drifted. `TFTDisplay`'s centred header still stands as described + above, so the item holds for it. + 8. **The Ring's pad arc and LED arc coincide** — **resolved, with §11.9.** Both the solder pads and the LEDs were placed on the mid-radius circle, so the four pads sat on top of LEDs at the bottom of the ring. Nothing was hidden — `drawSolderPads` runs last, after the transform is restored, so @@ -174,11 +183,11 @@ Ordered by risk, not by file size. None of these is a compatibility concern (D6) | 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 | +| 6 | Replace raw pixel geometry with `Size` constants (D7) — **done for all five.** `CharacterLCD` was the last holdout and was unblocked by maintainer figures for its bezel, lit area and header placement; its window is now the part's own size rather than a margin off the board, and no pixel literal is left in `draw()`. One caveat worth keeping separate: its mounting-hole inset and diameter are *named* constants but still the draft's guessed 2.54 mm, so D7 is satisfied while §11.8's sourcing question is not | `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), `WS2812BRing`'s diameters — **all six of which were wrong**, see §3.2 item 10 — and that same class's pad count, naming and arrangement (§11.9, maintainer-supplied, which also closed §3.2 item 8). Open: `SevenSegmentDisplay`'s pin arrays (§11.10), `CharacterLCD`'s part identity (§11.8), and `LEDMatrix8x8`'s silk placement (§3.2 item 9) | all 7 | the bulk of the slice | +| 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), `WS2812BRing`'s diameters — **all six of which were wrong**, see §3.2 item 10 — and that same class's pad count, naming and arrangement (§11.9, maintainer-supplied, which also closed §3.2 item 8), `SevenSegmentDisplay`'s pin arrays and part identities (§11.10 — 5161AS, 3641AS and 5641AS, with the two four-digit parts confirmed to share a pinout), and `CharacterLCD`'s bodies, bezel, lit area, header placement and hole inset (§11.8). Closed by decision rather than by measurement: `SevenSegmentDisplay`'s digit widths, accepted as inferences because they size only the drawn glyph, which already departs from the package deliberately (§11.11). Open: `CharacterLCD`'s hole diameter (§11.8) and `LEDMatrix8x8`'s silk placement (§3.2 item 9) | 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 @@ -504,8 +513,8 @@ gate the displays. 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 +8. **`CharacterLCD`'s geometry could not be sourced consistently** — **now resolved except the + mounting holes.** This is why item 6 was 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 @@ -516,6 +525,31 @@ gate the displays. 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. + + **The outlines are now maintainer-given: 80 × 35 mm for the 16x2, 98 × 60 mm for the 20x4.** + That settles the first of the three figures this item needs, correcting the 16x2's height by a + millimetre and confirming the 20x4 exactly as stored. It does **not** unblock prep item 6. The + window and the mounting holes are still raw pixels with no source behind them: the bezel is + derived as `boardH - 65` px, which on the corrected board still fills about 76% of its height + when a 16 mm window on a 35 mm board should be under half. A one-millimetre change to the board + cannot fix a bezel whose size does not come from the board at all. What is still wanted is the + display window and the hole positions, ideally from one named part. + + **The window figures have since landed.** The 16x2 carries a 72.2 × 24.1 mm bezel around a + 64.5 × 14.5 mm lit area; the 20x4 a 77 × 25.5 mm bezel around 70.4 × 20.4 mm. Both are fields on + `LCDSize` now, drawn centred on the board and within the bezel respectively. That is the actual + fix rather than a retuning: the window is the part's own size instead of a margin subtracted + from the board, so it no longer grows when the board does — which is why the earlier 1 mm board + correction could not have helped it. + + **The hole inset has since been measured: 2.5 mm from each edge.** Note that this was a + correction rather than a confirmation — the constant held the draft's 2.54 mm, inherited from + the raw `20` px, and 2.54 is a suspiciously convenient number to inherit because it is exactly + 0.1", so it had the shape of a value chosen for the grid rather than for the part. + + **What stays open is the hole diameter alone**, still 2.54 mm and still a guess. It is the only + unsourced figure left in `CharacterLCD`, and it is a cosmetic detail rather than a placement + error, so the class is otherwise finished. 9. **How many pads does a NeoPixel Ring have?** — **decided, from the maintainer: four on the 12-LED ring, six on the 16 and the 24.** The guess recorded here, that the 2014 "extra ground and power breakout" made six universal, was half right — the two larger rings gained the extra pair @@ -550,17 +584,27 @@ gate the displays. both were misreading the board's own anti-aliased edge, which registers at every angle rather than only at the pads. Magnifying the tightest pad on each variant 12× showed unbroken board between every pad and the rim. -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. **With §11.9 settled this is the last unsourced - geometry in the set, and the worse one to leave** — 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. +10. **`SevenSegmentDisplay`'s pin arrays** — **decided: the maintainer confirms they are correct.** + Three questions closed together. The arrays are right as they stand; the two bare four-digit + packages **share a pinout**; and each variant is now pinned to a named part — **5161AS** for the + single digit, **3641AS** for the 0.36" four-digit package and **5641AS** for the 0.56" one. + + That also retires the suspicion recorded here before. The worry had been that the 12-pin array + followed a 0.36" part while the dimensions described a 0.56" one, pairing one part's pins with + another part's body. Since the two parts share pins, that pairing was never the error it looked + like — though the dimensions themselves are a separate question, still open in §11.11. + + Worth being precise about what kind of confirmation this is. The order was corrected earlier + from the maintainer's own reading of the parts, after it was found transcribed in reading order + rather than DIP order, which had put every segment and digit common on the wrong pin. It has now + been confirmed as correct. No datasheet was ever obtained — both xlitx pages carry only + electro-optical tables and both linked PDFs are image-encoded — so this rests on the + maintainer's word rather than on a published pinout. That is good enough to close the item, and + it is a different claim from "verified against a datasheet". + + Naming the parts matters beyond this item. It is the move that settled the TFT on the MSP2807, + it is what §11.8 still needs for `CharacterLCD`, and it turns the gap left in §11.11 from + unanswerable into merely unmeasured. 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 @@ -576,14 +620,46 @@ gate the displays. 11. **What the enum split left unsourced.** The split itself is sound; what follows is geometry that should not be mistaken for measurement. + *A second round of maintainer figures has since landed.* The 5161AS body is 12.7 × 19.0 mm, a + 0.1 mm correction to the stored width; the 5641AS body (50.3 × 19.0 mm) and digit pitch + (12.7 mm) are confirmed exactly as stored; and the TM1637 module's dimensions are confirmed as a + set. That leaves a narrower gap than before but not an empty one: **every digit width is still + unmeasured** — 5.2 mm on the 0.36" part and 8.1 mm on both 0.56" ones — and the 5.2 mm is still + the only figure in the file invented outright rather than merely unchecked. + + *The apparent height conflict is resolved, and was never about the part.* The 5161AS is a + 0.56" part, the enum's 14.2 mm stands and so does the label. The 12.7 mm figure is the size the + digit is deliberately **drawn** at, held under the true one so the glyph keeps clear of the + pins — the same trade `DIGIT_DRAW_SCALE` exists for. + + **No value changed, because the existing mechanism already does it.** At a scale of 0.88 a + 14.2 mm digit draws at 12.50 mm, within 0.2 mm of the half inch intended. Forcing 12.7 exactly + would mean either raising the scale, which is global and would grow the 0.36" and TM1637 digits + too, or writing 12.7 into `digitHeight`, which would apply the shrink twice and draw at 11.2 mm. + The separation the class already draws — `DisplayType` holds what the package measures, + `DIGIT_DRAW_SCALE` holds the departure from it — is exactly the distinction the conflict + collapsed, which is why reading the drawing code dissolved it rather than deciding it. + *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 + (5.2 mm)**, derived by applying the 0.56" part's 8.1 : 14.2 ratio to a 9.14 mm digit — and the + 0.56" digit figures it borrowed from are themselves still unmeasured, 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. + rather than leaving as an inference — and cheaper to settle now that §11.10 has pinned this + variant to the 3641AS and the 0.56" one to the 5641AS, so there is a specific part to measure + rather than a generic four-digit display. + + **Decided: left as inferences, deliberately.** The digit widths are not being measured. They + size the drawn glyph and nothing else, and the glyph is already a declared departure from the + package at `DIGIT_DRAW_SCALE` 0.88, so an error here is cosmetic and bounded — unlike a wrong + pin array, which is silent and wrong in the netlist, or a wrong outline, which is wrong against + the board around it. The three figures stand as recorded: 5.2 mm inferred for the 0.36" part + and 8.1 mm unverified for both 0.56" ones. They stay flagged here precisely so that a later + reader does not mistake them for measurements and build something on top of them, which is how + the 5.2 mm came to exist in the first place. 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 @@ -595,3 +671,53 @@ gate the displays. 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. +12. **Common cathode vs common anode** — **decided: a `Common` property, reaching the BOM only.** + Both kinds are sold side by side and are identical in pinout, package and dimensions; the + difference is internal polarity. So the property changes nothing that is drawn and nothing in + the netlist — it exists to split two genuinely different parts into two BOM rows, which is + exactly what §11.1's `getVariantLabel()` hook was built for. A cell now reads + `1-Digit 0.56", Common Cathode`, following the comma pattern `CharacterLCD` already uses for its + two-property variant. + + Three details settled with it. The default is **cathode**, matching the AS-suffixed parts named + in §11.10. The **TM1637 omits it**, because the module drives the digits itself and brings out + no common pins, so polarity is not part of what you order and its cell stays + `4-Digit TM1637 Module`. And the **pin names are untouched**: `COM1`/`COM2` and `D1`-`D4` are + physical designations identical on both polarities, so renaming them by polarity would have + left the single-digit and four-digit parts labelling their pins on different principles for no + gain. + + One thing deliberately not done: putting the part number in the value. That would be the most + directly orderable form — `3641AS` against `3641BS` — but it rests on `AS` meaning cathode and + `BS` meaning anode for this family, which has **not** been confirmed from a datasheet. Spelling + the polarity out carries the same information without depending on an unverified convention. + + Adding the field now rather than after 6.7.0 is deliberate: the class has not shipped, so a new + serialized field costs nothing in file-format terms. It is still defaulted in the getter rather + than only at the field, per the house rule for fields that may deserialize as null. +13. **Decimal points and the colon** — **decided: a `Punctuation` property, drawn and in the BOM.** + The parts are sold in every combination, and the component had been drawing one fixed + combination — a decimal point on every digit *and* a colon — on all four variants at once. + + That combination is not merely a default, it is one a twelve-pin package cannot have. `A`-`G` + plus `DP` is eight segment lines and `D1`-`D4` is four commons, which accounts for all twelve + pins confirmed in §11.10, leaving nothing to drive a colon independently: on a real part the + colon shares the decimal point's line or replaces it. So the drawing depicted a part that its + own pinout rules out. The property describes the face of the package rather than a fifth pin, + which is why it does not touch `PIN_NAMES_4DIGIT`. + + `Punctuation` has four values — none, decimal points, colon, both — defaulting to decimal + points, which is what the committed twelve-pin array describes. It is **not** switched + automatically when `DisplayType` changes, for the reason recorded in §11.2: per-variant + defaults applied from a variant setter silently discard a choice the user has already made. + The colon is ignored on the single-digit package, following `boardColor` on the bare packages + and `Common` on the TM1637. + + One implementation trap worth recording. Suppressing a decimal point is **not** a matter of + passing `"8"` instead of `"8."`: the glyph routine draws an *unlit* dot whenever no lit one is + asked for, so the obvious change leaves a grey dot behind and looks correct at a glance. Both + the lit and the unlit path are gated on the new flag. + + It reaches the BOM for the same reason `Common` does — it changes which part you order — so a + cell now reads `4-Digit 0.36", Common Cathode, Colon`. That is three tokens, and the value + column is getting long; worth revisiting if a fourth ever appears.