This commit is contained in:
Bane Stojkovic
2026-10-01 12:32:41 +02:00
parent e2ea25874e
commit 4062c4d7a9
4 changed files with 60 additions and 41 deletions
@@ -95,6 +95,12 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
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");
// The colour wheel addressable RGB parts are drawn lit with, shared so a ring, a stick and a
// strip all run the same sequence. It is treated as a loop, so the last anchor runs back into
// the first and a gradient spread over any LED count closes without a seam.
public static Color[] RGB_LED_GRADIENT = new Color[] {
Color.decode("#FFEE00"), Color.decode("#FF8800"), Color.decode("#FF1122"),
Color.decode("#AA22EE"), Color.decode("#2255FF"), Color.decode("#22DD44")};
public static Color SILK_COLOR = Color.WHITE;
public static Size PIN_SIZE = new Size(0.04d, SizeUnit.in);
@@ -572,6 +578,28 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
return new Size(0.035d, SizeUnit.in);
}
/**
* Spreads {@link #RGB_LED_GRADIENT} evenly over {@code count} LEDs, interpolating between
* neighbouring anchors and treating them as a loop so the last LED runs back into the first.
* Called once per palette at class load rather than per repaint.
*/
protected static Color[] buildLedGradient(int count) {
Color[] colors = new Color[count];
int anchorCount = RGB_LED_GRADIENT.length;
for (int i = 0; i < count; i++) {
double position = (double) i * anchorCount / count;
int anchor = (int) position;
double blend = position - anchor;
Color from = RGB_LED_GRADIENT[anchor];
Color to = RGB_LED_GRADIENT[(anchor + 1) % anchorCount];
colors[i] = new Color(
(int) Math.round(from.getRed() + (to.getRed() - from.getRed()) * blend),
(int) Math.round(from.getGreen() + (to.getGreen() - from.getGreen()) * blend),
(int) Math.round(from.getBlue() + (to.getBlue() - from.getBlue()) * blend));
}
return colors;
}
protected void drawSolderPads(Graphics2D g2d, int startIndex, int count, boolean outlineMode, IDrawingObserver drawingObserver) {
if (outlineMode) return;
double padW = getSolderPadWidth().convertToPixels();
@@ -121,16 +121,10 @@ public class WS2812BRing extends AbstractMakerBoard {
public static Size PAD_EDGE_INSET = new Size(1.4d, SizeUnit.mm);
// The ring is drawn lit: unlit it is a black disc with white specks on it, which reads as no
// particular part. The sequence runs yellow - orange - red - purple - blue - green and wraps
// back into yellow, so the gradient is continuous the whole way round rather than breaking at
// whichever LED happens to be first.
public static Color[] GRADIENT_ANCHORS = new Color[] {
Color.decode("#FFEE00"), Color.decode("#FF8800"), Color.decode("#FF1122"),
Color.decode("#AA22EE"), Color.decode("#2255FF"), Color.decode("#22DD44")};
public static Color[] LED_COLORS_12 = buildGradient(12);
public static Color[] LED_COLORS_16 = buildGradient(16);
public static Color[] LED_COLORS_24 = buildGradient(24);
// particular part. One palette per variant, off the shared colour wheel.
public static Color[] LED_COLORS_12 = buildLedGradient(12);
public static Color[] LED_COLORS_16 = buildLedGradient(16);
public static Color[] LED_COLORS_24 = buildLedGradient(24);
private RingSize ringSize = RingSize._16_LED;
@@ -188,28 +182,6 @@ public class WS2812BRing extends AbstractMakerBoard {
return (outerR + innerR) / 2.0;
}
/**
* Spreads {@link #GRADIENT_ANCHORS} evenly over {@code count} LEDs, interpolating between
* neighbouring anchors and treating the anchors as a loop so the last LED runs back into the
* first. Called once per palette at class load rather than per repaint.
*/
private static Color[] buildGradient(int count) {
Color[] colors = new Color[count];
int anchorCount = GRADIENT_ANCHORS.length;
for (int i = 0; i < count; i++) {
double position = (double) i * anchorCount / count;
int anchor = (int) position;
double blend = position - anchor;
Color from = GRADIENT_ANCHORS[anchor];
Color to = GRADIENT_ANCHORS[(anchor + 1) % anchorCount];
colors[i] = new Color(
(int) Math.round(from.getRed() + (to.getRed() - from.getRed()) * blend),
(int) Math.round(from.getGreen() + (to.getGreen() - from.getGreen()) * blend),
(int) Math.round(from.getBlue() + (to.getBlue() - from.getBlue()) * blend));
}
return colors;
}
private Color[] getLedColors() {
switch (ringSize) {
case _12_LED:
@@ -220,7 +192,7 @@ public class WS2812BRing extends AbstractMakerBoard {
return LED_COLORS_24;
default:
// a ring size added later still renders before it is given a palette of its own
return buildGradient(ringSize.getLedCount());
return buildLedGradient(ringSize.getLedCount());
}
}
@@ -61,6 +61,9 @@ public class WS2812BStick extends AbstractMakerBoard {
public static Size BOARD_HEIGHT = new Size(10.22d, SizeUnit.mm);
public static Size PAD_INSET = new Size(2.0d, SizeUnit.mm);
// Drawn lit, off the same colour wheel as the ring, so the two read as the same family of part.
public static Color[] LED_COLORS_8 = buildLedGradient(8);
// 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.
@@ -154,14 +157,22 @@ public class WS2812BStick extends AbstractMakerBoard {
g2d.draw(boardShape);
if (!outlineMode) {
// 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.
// Eight 5mm packages spread evenly between the two pad columns rather than packed edge to
// edge. The field has to clear the header block drawPinHeader paints around each pin, which
// reaches one pin width plus a pixel either side of the control point and is drawn after the
// LEDs -- starting the field at the pad inset alone put that block over the first and last
// package. The pad names are printed on the back of the real board, so this face carries no
// silkscreen.
double ledSize = RGB_LED_SIZE.convertToPixels();
double fieldInset = (boardW - 8 * ledSize) / 2.0;
double padInset = PAD_INSET.convertToPixels();
double headerHalf = PIN_SIZE.convertToPixels() + 1;
double fieldStart = boardX + padInset + headerHalf;
double fieldEnd = boardX + boardW - padInset - headerHalf;
double pitch = (fieldEnd - fieldStart - ledSize) / 7.0;
for (int i = 0; i < 8; i++) {
MakerBoardPainter.drawAddressableLed(g2d, boardX + fieldInset + (i + 0.5) * ledSize,
boardY + boardH / 2.0, ledSize);
MakerBoardPainter.drawAddressableLed(g2d, fieldStart + ledSize / 2.0 + i * pitch,
boardY + boardH / 2.0, ledSize, LED_COLORS_8[i]);
}
}
+11 -3
View File
@@ -51,7 +51,7 @@ Seven classes, all `category = "Displays & Outputs"`, all `bomPolicy = SHOW_ONLY
| `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. |
| `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. All since corrected: a `Size`-based 51.1 × 10.22 mm board, real 5 mm 5050 packages off the shared `RGB_LED_SIZE`, pads reordered and renamed for uniqueness, and the LED row respread between the pad columns and drawn lit from the shared colour wheel (§11.6). |
### 3.1 Two defects that are bugs, not style
@@ -491,8 +491,16 @@ gate the displays.
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.
eight 5 mm packages on the 51.1 mm board — and the field is derived rather than tuned to leave
text room, so `LED_FIELD_INSET` is gone.
The row was respread afterwards. The packages had been packed edge to edge at exactly one
package width; they are now spaced evenly between the two pad columns, and the field starts
clear of the header block `drawPinHeader` paints around each pin. That block reaches a pin width
plus a pixel either side of the control point and is drawn after the LEDs, so deriving the field
from `PAD_INSET` alone left it covering 1.14 mm of the first and last package — measured off the
render, not guessed. The Stick is also drawn lit now, off the same shared colour wheel as the
Ring, so the two read as one family of part.
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.