mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
neopixel improvements
This commit is contained in:
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
|
||||
/**
|
||||
* Base for the boards built out of addressable 5050 LEDs -- the rings, the jewel, the stick, the
|
||||
* strip, the matrix and the bare breakout.
|
||||
*
|
||||
* <p>Every one of them is sold fitted with any of the packages {@link LedType} lists, on the same
|
||||
* PCB with the same footprint, so the package is a property of the board rather than a board of
|
||||
* its own. What it changes is what the LEDs can emit, which is the palette here, and what the BOM
|
||||
* has to ask for, which each board folds into its own variant label.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public abstract class AbstractAddressableLedBoard extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected LedType ledType = LedType.RGB;
|
||||
|
||||
// Rebuilt only when the count or the package changes, because draw() runs on every repaint.
|
||||
private transient Color[] ledColors;
|
||||
|
||||
@EditableProperty(name = "LED Type")
|
||||
public LedType getLedType() {
|
||||
if (ledType == null) {
|
||||
ledType = LedType.RGB;
|
||||
}
|
||||
return ledType;
|
||||
}
|
||||
|
||||
public void setLedType(LedType ledType) {
|
||||
this.ledType = ledType;
|
||||
this.ledColors = null;
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* The colours {@code count} LEDs are drawn lit with. These parts are drawn lit because unlit
|
||||
* they are a dark board with white specks on it, which reads as no particular component; running
|
||||
* the palette around the part also says at a glance which package it is fitted with, a WWA one
|
||||
* having no colour to show.
|
||||
*/
|
||||
protected Color[] getLedColors(int count) {
|
||||
if (ledColors == null || ledColors.length != count) {
|
||||
ledColors = buildLedGradient(count, getLedType().getGradient());
|
||||
}
|
||||
return ledColors;
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,10 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
|
||||
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")};
|
||||
// A WWA package carries no colour die, so a part fitted with one cannot show the wheel above.
|
||||
// Its three anchors are the three dies themselves: warm white, white and amber.
|
||||
public static Color[] WWA_LED_GRADIENT = new Color[] {
|
||||
Color.decode("#FFE0BD"), Color.decode("#FFFFFF"), Color.decode("#FFAA00")};
|
||||
public static Color SILK_COLOR = Color.WHITE;
|
||||
|
||||
public static Size PIN_SIZE = new Size(0.04d, SizeUnit.in);
|
||||
@@ -625,14 +629,22 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
|
||||
* Called once per palette at class load rather than per repaint.
|
||||
*/
|
||||
protected static Color[] buildLedGradient(int count) {
|
||||
return buildLedGradient(count, RGB_LED_GRADIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* As above, over a palette of the caller's choosing, for a part whose LEDs cannot emit the
|
||||
* colour wheel.
|
||||
*/
|
||||
protected static Color[] buildLedGradient(int count, Color[] anchors) {
|
||||
Color[] colors = new Color[count];
|
||||
int anchorCount = RGB_LED_GRADIENT.length;
|
||||
int anchorCount = 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 = RGB_LED_GRADIENT[anchor];
|
||||
Color to = RGB_LED_GRADIENT[(anchor + 1) % anchorCount];
|
||||
Color from = anchors[anchor];
|
||||
Color to = 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),
|
||||
@@ -924,4 +936,34 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
|
||||
int margin = 50;
|
||||
return new Rectangle2D.Double(minX - margin, minY - margin, (maxX - minX) + 2 * margin, (maxY - minY) + 2 * margin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Which addressable package a part is fitted with. The three are pin-compatible and share the
|
||||
* 5050 footprint, so this changes nothing but what the LEDs can emit and what the BOM has to
|
||||
* ask for -- which is the whole reason it is a property rather than a separate component.
|
||||
*
|
||||
* <p>RGBW adds a fourth, white die to the RGB three, and is sold by the tint of that die; WWA
|
||||
* replaces the colour dies altogether with warm white, white and amber.
|
||||
*/
|
||||
public enum LedType {
|
||||
RGB("RGB"),
|
||||
RGBW_WARM("RGBW Warm White"),
|
||||
RGBW_NATURAL("RGBW Natural White"),
|
||||
RGBW_COOL("RGBW Cool White"),
|
||||
WWA("WWA");
|
||||
|
||||
private final String label;
|
||||
|
||||
LedType(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override public String toString() { return label; }
|
||||
|
||||
/** True for the packages that carry a white die beside the colour ones. */
|
||||
public boolean hasWhiteDie() { return this != RGB && this != WWA; }
|
||||
|
||||
/** The wheel a part fitted with this package is drawn lit with. */
|
||||
public Color[] getGradient() { return this == WWA ? WWA_LED_GRADIENT : RGB_LED_GRADIENT; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +274,17 @@ public class MakerBoardPainter {
|
||||
*/
|
||||
public static void drawAddressableLed(Graphics2D g2d, double cx, double cy, double size,
|
||||
Color litColor, double rotation) {
|
||||
drawAddressableLed(g2d, cx, cy, size, litColor, rotation, AbstractMakerBoard.LedType.RGB);
|
||||
}
|
||||
|
||||
/**
|
||||
* As above, for a part fitted with a package other than the plain RGB one. An RGBW package is
|
||||
* told from an RGB one on sight only by the white die sitting beside the colour dies, so that
|
||||
* is what is drawn; WWA has the same three-die layout as RGB and is told apart instead by what
|
||||
* it emits, which is the caller's palette rather than anything here.
|
||||
*/
|
||||
public static void drawAddressableLed(Graphics2D g2d, double cx, double cy, double size,
|
||||
Color litColor, double rotation, AbstractMakerBoard.LedType ledType) {
|
||||
if (rotation != 0) {
|
||||
g2d.rotate(rotation, cx, cy);
|
||||
}
|
||||
@@ -293,8 +304,16 @@ public class MakerBoardPainter {
|
||||
g2d.draw(new Ellipse2D.Double(cx - lensR, cy - lensR, lensR * 2, lensR * 2));
|
||||
|
||||
double dieSize = Math.max(2.0, lensR * 0.4);
|
||||
// the colour dies sit centred on their own, and shoulder aside to make room for a white one
|
||||
double dieOffset = ledType != null && ledType.hasWhiteDie() ? dieSize * 0.7 : 0;
|
||||
g2d.setColor(litColor == null ? AbstractMakerBoard.RGB_LED_CHIP_COLOR : litColor.brighter());
|
||||
g2d.fill(new Rectangle2D.Double(cx - dieSize / 2.0, cy - dieSize / 2.0, dieSize, dieSize));
|
||||
g2d.fill(new Rectangle2D.Double(cx - dieOffset - dieSize / 2.0, cy - dieSize / 2.0, dieSize,
|
||||
dieSize));
|
||||
if (dieOffset > 0) {
|
||||
g2d.setColor(litColor == null ? AbstractMakerBoard.RGB_LED_CHIP_COLOR : Color.WHITE);
|
||||
g2d.fill(new Rectangle2D.Double(cx + dieOffset - dieSize / 2.0, cy - dieSize / 2.0, dieSize,
|
||||
dieSize));
|
||||
}
|
||||
|
||||
// turned back rather than saving the transform, so nothing is allocated per LED
|
||||
if (rotation != 0) {
|
||||
|
||||
+10
-7
@@ -32,7 +32,7 @@ import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.AbstractAddressableLedBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -51,7 +51,7 @@ import org.diylc.utils.Constants;
|
||||
instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class WS2812BBreakout extends AbstractMakerBoard {
|
||||
public class WS2812BBreakout extends AbstractAddressableLedBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -74,10 +74,6 @@ public class WS2812BBreakout extends AbstractMakerBoard {
|
||||
public static Size MOUNTING_HOLE_SIZE = new Size(2.0d, SizeUnit.mm);
|
||||
public static Size MOUNTING_HOLE_SPACING = new Size(0.3d, SizeUnit.in);
|
||||
|
||||
// Drawn lit off the same colour wheel as the rest of the family, so a breakout on a layout reads
|
||||
// as the same part as one pixel of a stick.
|
||||
public static Color LED_COLOR = buildLedGradient(1)[0];
|
||||
|
||||
// Each edge carries ground, power and one data line. The two grounds are the same net on the
|
||||
// board, as are the two supplies, and are numbered only to keep the node names distinct.
|
||||
private static final String[] PIN_NAMES = {
|
||||
@@ -91,6 +87,11 @@ public class WS2812BBreakout extends AbstractMakerBoard {
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getLedType().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
if (index >= 0 && index < PIN_NAMES.length) {
|
||||
@@ -196,9 +197,11 @@ public class WS2812BBreakout extends AbstractMakerBoard {
|
||||
// The pad columns clear the package by 2.35 mm, but that room is between a pad and the
|
||||
// package rather than beside a pad, so there is nowhere a pin name would go: this face
|
||||
// carries no silkscreen, as the stick's does not.
|
||||
// Drawn lit off the same wheel as the rest of the family, so a breakout on a layout reads
|
||||
// as the same part as one pixel of a stick.
|
||||
Point2D led = getLedCentre();
|
||||
MakerBoardPainter.drawAddressableLed(g2d, led.getX(), led.getY(),
|
||||
RGB_LED_SIZE.convertToPixels(), LED_COLOR);
|
||||
RGB_LED_SIZE.convertToPixels(), getLedColors(1)[0], 0, getLedType());
|
||||
}
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.awt.geom.Point2D;
|
||||
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.AbstractAddressableLedBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -56,7 +56,7 @@ import org.diylc.utils.Constants;
|
||||
instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class WS2812BJewel extends AbstractMakerBoard {
|
||||
public class WS2812BJewel extends AbstractAddressableLedBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -81,16 +81,17 @@ public class WS2812BJewel extends AbstractMakerBoard {
|
||||
// so the sixth gap carries no pad -- it is the one due left, where the left mounting hole sits.
|
||||
private static final int[] PAD_GAP_INDEX = new int[] {0, 1, 2, 3, 4};
|
||||
|
||||
// Drawn lit, as the rings are: unlit it is a black disc with white specks on it, which reads
|
||||
// as no particular part.
|
||||
public static Color[] LED_COLORS = buildLedGradient(RING_LED_COUNT + 1);
|
||||
|
||||
public WS2812BJewel() {
|
||||
super();
|
||||
this.bodyColor = NEO_BLACK;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getLedType().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
if (index >= 0 && index < PAD_NAMES.length) {
|
||||
@@ -198,17 +199,19 @@ public class WS2812BJewel extends AbstractMakerBoard {
|
||||
|
||||
double ledR = LED_CIRCLE_DIAMETER.convertToPixels() / 2.0;
|
||||
double ledSize = RGB_LED_SIZE.convertToPixels();
|
||||
Color[] ledColors = getLedColors(RING_LED_COUNT + 1);
|
||||
LedType ledType = getLedType();
|
||||
|
||||
for (int i = 0; i < RING_LED_COUNT; i++) {
|
||||
double angle = 2 * Math.PI * i / RING_LED_COUNT - Math.PI / 2.0;
|
||||
MakerBoardPainter.drawAddressableLed(g2d, cx + ledR * Math.cos(angle),
|
||||
cy + ledR * Math.sin(angle), ledSize, LED_COLORS[i % LED_COLORS.length],
|
||||
angle + Math.PI / 2.0);
|
||||
cy + ledR * Math.sin(angle), ledSize, ledColors[i % ledColors.length],
|
||||
angle + Math.PI / 2.0, ledType);
|
||||
}
|
||||
|
||||
// the centre package has no radius to face along, so it stays square to the board
|
||||
MakerBoardPainter.drawAddressableLed(g2d, cx, cy, ledSize,
|
||||
LED_COLORS[RING_LED_COUNT % LED_COLORS.length]);
|
||||
ledColors[RING_LED_COUNT % ledColors.length], 0, ledType);
|
||||
|
||||
// Pad names are left to the node tooltips and the netlist: a pad sits in the gap between two
|
||||
// LEDs, which is nowhere near enough room for its name.
|
||||
|
||||
@@ -36,7 +36,7 @@ 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.AbstractAddressableLedBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -65,7 +65,7 @@ import org.diylc.utils.Constants;
|
||||
instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class WS2812BPanel extends AbstractMakerBoard {
|
||||
public class WS2812BPanel extends AbstractAddressableLedBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -115,12 +115,6 @@ public class WS2812BPanel extends AbstractMakerBoard {
|
||||
*/
|
||||
private static final double CAP_HEIGHT_RATIO = 0.72d;
|
||||
|
||||
// Drawn lit, as every addressable part in this package is. The colour is handed out along the
|
||||
// data chain rather than across the board, so the sweep shows the order the pixels are addressed
|
||||
// in: this panel is wired as a progressive raster, left to right along each row and then back to
|
||||
// the left of the next, not as the serpentine most cheap panels use.
|
||||
public static Color[] LED_COLORS = buildLedGradient(MATRIX_ORDER * MATRIX_ORDER);
|
||||
|
||||
/**
|
||||
* What the face of the board prints, from layer 21 of the board file. Every line lies in a gap
|
||||
* between two rows of pixels, which is the only bare board this part has: the grid reaches
|
||||
@@ -143,12 +137,12 @@ public class WS2812BPanel extends AbstractMakerBoard {
|
||||
}
|
||||
|
||||
/**
|
||||
* There is only one of these panels, so unlike a board with a variant property this is not
|
||||
* needed to keep the BOM's rows apart. It is still the better answer than an empty value column.
|
||||
* The panel comes in one size, so unlike a board with a geometry property the grid is not what
|
||||
* keeps the BOM's rows apart -- the package is.
|
||||
*/
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return "8x8, 64 LEDs";
|
||||
return "8x8, 64 LEDs, " + getLedType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -266,9 +260,16 @@ public class WS2812BPanel extends AbstractMakerBoard {
|
||||
double margin = (board - (MATRIX_ORDER - 1) * pitch) / 2.0;
|
||||
double ledSize = RGB_LED_SIZE.convertToPixels();
|
||||
|
||||
for (int i = 0; i < LED_COLORS.length; i++) {
|
||||
// The colour is handed out along the data chain rather than across the board, so the sweep
|
||||
// shows the order the pixels are addressed in: this panel is wired as a progressive raster,
|
||||
// left to right along each row and then back to the left of the next, not as the serpentine
|
||||
// most cheap panels use.
|
||||
int ledCount = MATRIX_ORDER * MATRIX_ORDER;
|
||||
Color[] ledColors = getLedColors(ledCount);
|
||||
LedType ledType = getLedType();
|
||||
for (int i = 0; i < ledCount; i++) {
|
||||
MakerBoardPainter.drawAddressableLed(g2d, boardX + margin + (i % MATRIX_ORDER) * pitch,
|
||||
boardY + margin + (i / MATRIX_ORDER) * pitch, ledSize, LED_COLORS[i]);
|
||||
boardY + margin + (i / MATRIX_ORDER) * pitch, ledSize, ledColors[i], 0, ledType);
|
||||
}
|
||||
|
||||
// The port names are printed on the back of the real board, so this face carries none.
|
||||
|
||||
@@ -35,7 +35,7 @@ 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.AbstractAddressableLedBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -54,7 +54,7 @@ import org.diylc.utils.Constants;
|
||||
instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class WS2812BRing extends AbstractMakerBoard {
|
||||
public class WS2812BRing extends AbstractAddressableLedBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -68,12 +68,6 @@ public class WS2812BRing extends AbstractMakerBoard {
|
||||
// stays the same if the pad footprint changes.
|
||||
public static Size PAD_LABEL_GAP = new Size(0.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. 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;
|
||||
|
||||
public WS2812BRing() {
|
||||
@@ -93,10 +87,11 @@ public class WS2812BRing extends AbstractMakerBoard {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
// Every ring is sold in each of the packages, so neither half identifies what to buy on its own.
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
RingSize ringSize = getRingSize();
|
||||
return ringSize == null ? null : ringSize.toString();
|
||||
return ringSize == null ? null : ringSize + ", " + getLedType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,20 +110,6 @@ public class WS2812BRing extends AbstractMakerBoard {
|
||||
return (outerR + innerR) / 2.0;
|
||||
}
|
||||
|
||||
private Color[] getLedColors() {
|
||||
switch (ringSize) {
|
||||
case _12_LED:
|
||||
return LED_COLORS_12;
|
||||
case _16_LED:
|
||||
return LED_COLORS_16;
|
||||
case _24_LED:
|
||||
return LED_COLORS_24;
|
||||
default:
|
||||
// a ring size added later still renders before it is given a palette of its own
|
||||
return buildLedGradient(ringSize.getLedCount());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads sit out towards the rim rather than on the circle the LEDs occupy. This has to be the one
|
||||
* place the radius is decided: {@link #updateControlPoints()} offsets every pad from the first
|
||||
@@ -226,7 +207,8 @@ public class WS2812BRing extends AbstractMakerBoard {
|
||||
int ledCount = ringSize.getLedCount();
|
||||
double ledR = getMidRadius();
|
||||
double ledSize = RGB_LED_SIZE.convertToPixels();
|
||||
Color[] ledColors = getLedColors();
|
||||
Color[] ledColors = getLedColors(ledCount);
|
||||
LedType ledType = getLedType();
|
||||
|
||||
// Draw all LEDs around the circular ring, each package turned to face out along its own
|
||||
// radius the way it is mounted rather than left square to the board
|
||||
@@ -234,7 +216,7 @@ public class WS2812BRing extends AbstractMakerBoard {
|
||||
double angle = 2 * Math.PI * i / ledCount - Math.PI / 2.0;
|
||||
MakerBoardPainter.drawAddressableLed(g2d, cx + ledR * Math.cos(angle),
|
||||
cy + ledR * Math.sin(angle), ledSize, ledColors[i % ledColors.length],
|
||||
angle + Math.PI / 2.0);
|
||||
angle + Math.PI / 2.0, ledType);
|
||||
}
|
||||
|
||||
// Pad names run along their own radius, reading out from the middle of the ring towards the
|
||||
|
||||
@@ -33,7 +33,7 @@ import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.AbstractAddressableLedBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -51,7 +51,7 @@ import org.diylc.utils.Constants;
|
||||
instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class WS2812BStick extends AbstractMakerBoard {
|
||||
public class WS2812BStick extends AbstractAddressableLedBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -75,8 +75,7 @@ public class WS2812BStick extends AbstractMakerBoard {
|
||||
public static Size MOUNTING_HOLE_SPACING = new Size(1.0d, SizeUnit.in);
|
||||
public static Size MOUNTING_HOLE_TOP_OFFSET = 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);
|
||||
public static final int LED_COUNT = 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
|
||||
@@ -92,6 +91,11 @@ public class WS2812BStick extends AbstractMakerBoard {
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getLedType().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
if (index >= 0 && index < PIN_NAMES.length) {
|
||||
@@ -145,7 +149,7 @@ public class WS2812BStick extends AbstractMakerBoard {
|
||||
double inset = PAD_WIDTH.convertToPixels() + PAD_CLEARANCE.convertToPixels();
|
||||
double fieldStart = boardX + inset;
|
||||
double fieldEnd = boardX + BOARD_WIDTH.convertToPixels() - inset;
|
||||
double pitch = (fieldEnd - fieldStart - ledSize) / (LED_COLORS_8.length - 1);
|
||||
double pitch = (fieldEnd - fieldStart - ledSize) / (LED_COUNT - 1);
|
||||
|
||||
return new Point2D.Double(fieldStart + ledSize / 2.0 + index * pitch,
|
||||
(getHoleCentre(false).getY() + MOUNTING_HOLE_SIZE.convertToPixels() / 2.0 + boardY
|
||||
@@ -208,9 +212,12 @@ public class WS2812BStick extends AbstractMakerBoard {
|
||||
// than under them. The pad names are printed on the back of the real board, so this face
|
||||
// carries no silkscreen.
|
||||
double ledSize = RGB_LED_SIZE.convertToPixels();
|
||||
for (int i = 0; i < LED_COLORS_8.length; i++) {
|
||||
Color[] ledColors = getLedColors(LED_COUNT);
|
||||
LedType ledType = getLedType();
|
||||
for (int i = 0; i < LED_COUNT; i++) {
|
||||
Point2D led = getLedCentre(i);
|
||||
MakerBoardPainter.drawAddressableLed(g2d, led.getX(), led.getY(), ledSize, LED_COLORS_8[i]);
|
||||
MakerBoardPainter.drawAddressableLed(g2d, led.getX(), led.getY(), ledSize, ledColors[i], 0,
|
||||
ledType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ 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.AbstractAddressableLedBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -56,7 +56,7 @@ import org.diylc.utils.Constants;
|
||||
instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class WS2812BStrip extends AbstractMakerBoard {
|
||||
public class WS2812BStrip extends AbstractAddressableLedBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -106,9 +106,6 @@ public class WS2812BStrip extends AbstractMakerBoard {
|
||||
private Density density = Density._60;
|
||||
private int ledCount = DEFAULT_LED_COUNT;
|
||||
|
||||
// Rebuilt only when the count changes, because draw() runs on every repaint.
|
||||
private transient Color[] ledColors;
|
||||
|
||||
public WS2812BStrip() {
|
||||
super();
|
||||
this.bodyColor = TAPE_WHITE;
|
||||
@@ -139,15 +136,14 @@ public class WS2812BStrip extends AbstractMakerBoard {
|
||||
|
||||
public void setLedCount(int ledCount) {
|
||||
this.ledCount = Math.max(MIN_LED_COUNT, Math.min(MAX_LED_COUNT, ledCount));
|
||||
this.ledColors = null;
|
||||
updateControlPoints();
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
// Both decide what you buy and how much of it, so the BOM carries them together.
|
||||
// All three decide what you buy and how much of it, so the BOM carries them together.
|
||||
@Override
|
||||
protected String getVariantLabel() {
|
||||
return getDensity() + ", " + getLedCount() + " LEDs";
|
||||
return getDensity() + ", " + getLedCount() + " LEDs, " + getLedType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -187,14 +183,6 @@ public class WS2812BStrip extends AbstractMakerBoard {
|
||||
return (getLedCount() - 1) * getPitch() + 2 * getLeadIn();
|
||||
}
|
||||
|
||||
private Color[] getLedColors() {
|
||||
int count = getLedCount();
|
||||
if (ledColors == null || ledColors.length != count) {
|
||||
ledColors = buildLedGradient(count);
|
||||
}
|
||||
return ledColors;
|
||||
}
|
||||
|
||||
/** The span a column of three pads occupies across the tape, at the standard pad pitch. */
|
||||
private static double getPadRunLength() {
|
||||
return 2 * PIN_SPACING.convertToPixels();
|
||||
@@ -340,11 +328,12 @@ public class WS2812BStrip extends AbstractMakerBoard {
|
||||
double pitch = getPitch();
|
||||
double ledSize = RGB_LED_SIZE.convertToPixels();
|
||||
double firstCenter = boardX + getLeadIn();
|
||||
Color[] colors = getLedColors();
|
||||
int count = getLedCount();
|
||||
Color[] colors = getLedColors(count);
|
||||
LedType ledType = getLedType();
|
||||
for (int i = 0; i < count; i++) {
|
||||
MakerBoardPainter.drawAddressableLed(g2d, firstCenter + i * pitch, boardY + tapeH / 2.0,
|
||||
ledSize, colors[i]);
|
||||
ledSize, colors[i], 0, ledType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -215,4 +215,9 @@ public class WS2812BBreakoutTest {
|
||||
public void drawsCleanlyInEveryState() {
|
||||
MakerBoardTestSupport.assertDrawsCleanly(new WS2812BBreakout());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void honoursTheLedPackage() {
|
||||
MakerBoardTestSupport.assertAddressableLedTypes(WS2812BBreakout::new);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-9
@@ -26,6 +26,7 @@ import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.junit.Assert;
|
||||
@@ -237,19 +238,15 @@ public class WS2812BJewelTest {
|
||||
holeR * 2, holeR * 2));
|
||||
}
|
||||
|
||||
/** Seven LEDs get seven colours, so the centre one is lit rather than left dark. */
|
||||
/** The package it is fitted with is the only thing to tell a buyer about this one. */
|
||||
@Test
|
||||
public void thePaletteCoversAllSevenLeds() {
|
||||
Assert.assertEquals(WS2812BJewel.RING_LED_COUNT + 1, WS2812BJewel.LED_COLORS.length);
|
||||
for (int i = 0; i < WS2812BJewel.LED_COLORS.length; i++) {
|
||||
Assert.assertNotNull("colour " + i, WS2812BJewel.LED_COLORS[i]);
|
||||
}
|
||||
public void bomValueNamesThePackage() {
|
||||
Assert.assertEquals("RGB", new WS2812BJewel().getValueForDisplay());
|
||||
}
|
||||
|
||||
/** A single-variant board has no variant to put in the BOM's value column. */
|
||||
@Test
|
||||
public void bomValueIsEmpty() {
|
||||
Assert.assertEquals("", new WS2812BJewel().getValueForDisplay());
|
||||
public void honoursTheLedPackage() {
|
||||
MakerBoardTestSupport.assertAddressableLedTypes(WS2812BJewel::new);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+6
-1
@@ -235,7 +235,7 @@ public class WS2812BPanelTest {
|
||||
|
||||
@Test
|
||||
public void bomValueNamesTheMatrix() {
|
||||
Assert.assertEquals("8x8, 64 LEDs", new WS2812BPanel().getValueForDisplay());
|
||||
Assert.assertEquals("8x8, 64 LEDs, RGB", new WS2812BPanel().getValueForDisplay());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,4 +277,9 @@ public class WS2812BPanelTest {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void honoursTheLedPackage() {
|
||||
MakerBoardTestSupport.assertAddressableLedTypes(WS2812BPanel::new);
|
||||
}
|
||||
}
|
||||
|
||||
+70
-2
@@ -23,10 +23,12 @@ package org.diylc.components.displays;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.components.AbstractMakerBoard.LedType;
|
||||
import org.diylc.components.displays.WS2812BRing.RingSize;
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.junit.Assert;
|
||||
@@ -119,14 +121,80 @@ public class WS2812BRingTest {
|
||||
for (RingSize ringSize : RingSize.values()) {
|
||||
WS2812BRing ring = of(ringSize);
|
||||
Assert.assertEquals(ringSize, ring.getRingSize());
|
||||
Assert.assertEquals(ringSize.toString(), ring.getValueForDisplay());
|
||||
Assert.assertTrue(ring.getValueForDisplay().startsWith(ringSize.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
/** A ring saved before the property existed deserializes without one and has to read as RGB. */
|
||||
@Test
|
||||
public void ledTypeDefaultsToRgb() {
|
||||
Assert.assertEquals(LedType.RGB, new WS2812BRing().getLedType());
|
||||
WS2812BRing ring = new WS2812BRing();
|
||||
ring.setLedType(null);
|
||||
Assert.assertEquals(LedType.RGB, ring.getLedType());
|
||||
}
|
||||
|
||||
/** Both halves name a different thing to buy, so the BOM's value column has to carry both. */
|
||||
@Test
|
||||
public void variantLabelCarriesSizeAndLedType() {
|
||||
for (RingSize ringSize : RingSize.values()) {
|
||||
for (LedType ledType : LedType.values()) {
|
||||
WS2812BRing ring = of(ringSize);
|
||||
ring.setLedType(ledType);
|
||||
Assert.assertEquals(ringSize + ", " + ledType, ring.getValueForDisplay());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The packages are pin-compatible, so fitting a different one moves no pad. */
|
||||
@Test
|
||||
public void ledTypeLeavesTheFootprintAlone() {
|
||||
for (RingSize ringSize : RingSize.values()) {
|
||||
WS2812BRing reference = of(ringSize);
|
||||
for (LedType ledType : LedType.values()) {
|
||||
WS2812BRing ring = of(ringSize);
|
||||
ring.setLedType(ledType);
|
||||
MakerBoardTestSupport.assertSharedFootprint(reference, ring, ring.getControlPointCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RGBW shows the white die the RGB package does not carry, and WWA cannot emit the colour wheel
|
||||
* the other two are drawn lit with. Comparing renderings is what makes this a check on the
|
||||
* drawing: a draw() that ignored the property would pass the getter.
|
||||
*/
|
||||
@Test
|
||||
public void eachPackageIsDrawnDifferently() {
|
||||
int[] rgb = renderWith(LedType.RGB);
|
||||
Assert.assertFalse("RGBW should show its white die, and does not",
|
||||
Arrays.equals(rgb, renderWith(LedType.RGBW_WARM)));
|
||||
Assert.assertFalse("A WWA ring should not be drawn lit in colour",
|
||||
Arrays.equals(rgb, renderWith(LedType.WWA)));
|
||||
}
|
||||
|
||||
/** The three RGBW tints differ only in what you buy, so they are drawn the same. */
|
||||
@Test
|
||||
public void theRgbwTintsShareOneDrawing() {
|
||||
int[] warm = renderWith(LedType.RGBW_WARM);
|
||||
Assert.assertArrayEquals(warm, renderWith(LedType.RGBW_NATURAL));
|
||||
Assert.assertArrayEquals(warm, renderWith(LedType.RGBW_COOL));
|
||||
}
|
||||
|
||||
private static int[] renderWith(LedType ledType) {
|
||||
WS2812BRing ring = of(RingSize._16_LED);
|
||||
ring.setLedType(ledType);
|
||||
return MakerBoardTestSupport.renderPixels(ring);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drawsCleanlyInEveryState() {
|
||||
for (RingSize ringSize : RingSize.values()) {
|
||||
MakerBoardTestSupport.assertDrawsCleanly(of(ringSize));
|
||||
for (LedType ledType : LedType.values()) {
|
||||
WS2812BRing ring = of(ringSize);
|
||||
ring.setLedType(ledType);
|
||||
MakerBoardTestSupport.assertDrawsCleanly(ring);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,4 +194,9 @@ public class WS2812BStickTest {
|
||||
public void drawsCleanlyInEveryState() {
|
||||
MakerBoardTestSupport.assertDrawsCleanly(new WS2812BStick());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void honoursTheLedPackage() {
|
||||
MakerBoardTestSupport.assertAddressableLedTypes(WS2812BStick::new);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.Set;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.displays.WS2812BStrip.Density;
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.junit.Assert;
|
||||
@@ -288,4 +289,9 @@ public class WS2812BStripTest {
|
||||
private static double mmToPx(double value) {
|
||||
return new Size(value, SizeUnit.mm).convertToPixels();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void honoursTheLedPackage() {
|
||||
MakerBoardTestSupport.assertAddressableLedTypes(WS2812BStrip::new);
|
||||
}
|
||||
}
|
||||
|
||||
+51
@@ -26,9 +26,12 @@ import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.AbstractAddressableLedBoard;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.AbstractMakerBoard.LedType;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.Project;
|
||||
@@ -156,6 +159,54 @@ public class MakerBoardTestSupport {
|
||||
Arrays.equals(renderPixels(withText), renderPixels(withoutText)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a board built out of addressable LEDs honours the package it is fitted with.
|
||||
*
|
||||
* <p>Four things have to hold for every one of them, so they are asserted here rather than six
|
||||
* times over: a board saved before the property existed reads as RGB, the packages are
|
||||
* pin-compatible so fitting one in place of another moves no pad, RGBW is drawn with the white
|
||||
* die that is the only thing telling it from RGB on sight, and WWA is drawn off the warm white
|
||||
* palette rather than the colour wheel it cannot emit. The last two compare renderings, which is
|
||||
* what makes them checks on the drawing: a draw() that ignored the property would pass a getter.
|
||||
*
|
||||
* @param factory Builds a fresh board, since each assertion needs one of its own
|
||||
*/
|
||||
public static void assertAddressableLedTypes(Supplier<AbstractAddressableLedBoard> factory) {
|
||||
Assert.assertEquals("A board with no stored package should read as RGB", LedType.RGB,
|
||||
factory.get().getLedType());
|
||||
AbstractAddressableLedBoard stale = factory.get();
|
||||
stale.setLedType(null);
|
||||
Assert.assertEquals("A board saved before the property existed should read as RGB",
|
||||
LedType.RGB, stale.getLedType());
|
||||
|
||||
AbstractAddressableLedBoard reference = factory.get();
|
||||
for (LedType ledType : LedType.values()) {
|
||||
AbstractAddressableLedBoard board = factory.get();
|
||||
board.setLedType(ledType);
|
||||
assertSharedFootprint(reference, board, reference.getControlPointCount());
|
||||
Assert.assertTrue(ledType + " should be named in the BOM's value column",
|
||||
board.getValueForDisplay().contains(ledType.toString()));
|
||||
}
|
||||
|
||||
int[] rgb = renderWithLedType(factory, LedType.RGB);
|
||||
Assert.assertFalse("RGBW should show the white die RGB does not carry",
|
||||
Arrays.equals(rgb, renderWithLedType(factory, LedType.RGBW_WARM)));
|
||||
Assert.assertFalse("WWA should not be drawn lit in colour",
|
||||
Arrays.equals(rgb, renderWithLedType(factory, LedType.WWA)));
|
||||
|
||||
// the three RGBW tints name different parts to buy but the same thing to draw
|
||||
int[] warm = renderWithLedType(factory, LedType.RGBW_WARM);
|
||||
Assert.assertArrayEquals(warm, renderWithLedType(factory, LedType.RGBW_NATURAL));
|
||||
Assert.assertArrayEquals(warm, renderWithLedType(factory, LedType.RGBW_COOL));
|
||||
}
|
||||
|
||||
private static int[] renderWithLedType(Supplier<AbstractAddressableLedBoard> factory,
|
||||
LedType ledType) {
|
||||
AbstractAddressableLedBoard board = factory.get();
|
||||
board.setLedType(ledType);
|
||||
return renderPixels(board);
|
||||
}
|
||||
|
||||
/** Draws the board in every state and orientation; anything that throws fails the test. */
|
||||
public static void assertDrawsCleanly(AbstractMakerBoard board) {
|
||||
BufferedImage image = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Reference in New Issue
Block a user