mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
netlist and node name improvements
This commit is contained in:
@@ -411,6 +411,23 @@ public class NetlistBuilder {
|
||||
if (((IContinuity) c).arePointsConnected(i, j))
|
||||
connections.add(new Connection(c.getControlPoint(i), c.getControlPoint(j), z));
|
||||
}
|
||||
// Pins of one component that name the same rail are the same node: a board's eight ground
|
||||
// pins land on one plane, and grounding one of them must not leave the rest floating.
|
||||
// Switches are excluded because their connectivity depends on the position, which a name
|
||||
// cannot express -- a switch states it through ISwitch.arePointsConnected instead.
|
||||
if (!(c instanceof ISwitch)) {
|
||||
for (int i = 0; i < c.getControlPointCount() - 1; i++) {
|
||||
String rail = Node.railName(c.getControlPointNodeName(i));
|
||||
if (rail == null) {
|
||||
continue;
|
||||
}
|
||||
for (int j = i + 1; j < c.getControlPointCount(); j++) {
|
||||
if (rail.equals(Node.railName(c.getControlPointNodeName(j)))) {
|
||||
connections.add(new Connection(c.getControlPoint(i), c.getControlPoint(j), z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle switches
|
||||
if (ISwitch.class.isAssignableFrom(type.getInstanceClass())
|
||||
&& switchPositions.containsKey(c)) {
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
package org.diylc.netlist;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.core.ICommonNode;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -34,6 +37,10 @@ import org.diylc.core.IDIYComponent;
|
||||
*/
|
||||
public class Node implements Comparable<Node> {
|
||||
|
||||
/** Pin names that mark an absent connection, so repeats of them are not one net. */
|
||||
private static final Set<String> NON_NET_NAMES =
|
||||
new HashSet<String>(Arrays.asList("NC", "RSV"));
|
||||
|
||||
private IDIYComponent<?> component;
|
||||
private int pointIndex;
|
||||
|
||||
@@ -60,8 +67,9 @@ public class Node implements Comparable<Node> {
|
||||
* <code>MCU1.D11</code> rather than <code>MCU1.D11 (~, MOSI)</code>. The bare name identifies the
|
||||
* pin; everything in parentheses documents its alternate functions and only adds noise here.
|
||||
*
|
||||
* <p>A trailing disambiguator such as the <code>_1</code> in <code>GND_1</code> is deliberately
|
||||
* kept, because those mark genuinely different pins that a netlist has to tell apart.
|
||||
* <p>A trailing disambiguator such as the <code>_1</code> in <code>GND_1</code> is kept, so the
|
||||
* netlist can still say which pad a connection reached. Whether two such pins are the same
|
||||
* net is a separate question, answered by {@link #railName(String)}.
|
||||
*
|
||||
* @param nodeName raw name from {@link IDIYComponent#getControlPointNodeName(int)}
|
||||
* @return the sanitized name, or null if the raw name was null
|
||||
@@ -75,6 +83,28 @@ public class Node implements Comparable<Node> {
|
||||
// Never let sanitizing leave a pin nameless
|
||||
return sanitized.isEmpty() ? nodeName.trim() : sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rail a pin belongs to: its sanitized name with a trailing <code>_1</code>, <code>_2</code>
|
||||
* disambiguator removed, so <code>GND_1</code> and <code>GND_2</code> report the same rail.
|
||||
*
|
||||
* <p>Only a numeric suffix is dropped. An underscore does three unrelated jobs across the
|
||||
* pinouts in this library: it disambiguates a repeated rail, it namespaces a connector's signals
|
||||
* (<code>ETH_TX-</code>, <code>USB_D+</code>), and it qualifies a genuinely separate net
|
||||
* (<code>MISO_16U2</code>, <code>3V3_EN</code>). Cutting at the first underscore instead would
|
||||
* report an Ethernet header as one rail.
|
||||
*
|
||||
* @param nodeName raw name from {@link IDIYComponent#getControlPointNodeName(int)}
|
||||
* @return the rail name, or null if the pin has no name or names no net at all
|
||||
*/
|
||||
public static String railName(String nodeName) {
|
||||
String sanitized = sanitizeNodeName(nodeName);
|
||||
if (sanitized == null || sanitized.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String rail = sanitized.replaceAll("_\\d+$", "");
|
||||
return NON_NET_NAMES.contains(rail) ? null : rail;
|
||||
}
|
||||
|
||||
public Point2D getPoint2D() {
|
||||
return component.getControlPoint(pointIndex);
|
||||
|
||||
@@ -565,6 +565,14 @@
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Bill of Materials now lists the selected variant of microcontroller boards and displays, so different versions of the same board are no longer merged into a single row</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>4-pin Jumbo Tube Socket - the two filament pins are now labeled F1 and F2 instead of both being labeled F, so they no longer read as the same node</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Netlists now treat the repeated supply pins of a microcontroller or display board as one node, so grounding a single GND pin no longer leaves the others floating</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</changes>
|
||||
<url>https://github.com/bancika/diy-layout-creator/releases</url>
|
||||
</org.diylc.appframework.update.Version>
|
||||
|
||||
@@ -48,4 +48,41 @@ public class NodeNameTest {
|
||||
Assert.assertEquals("(only annotation)", Node.sanitizeNodeName("(only annotation)"));
|
||||
Assert.assertNull(Node.sanitizeNodeName(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void railNameDropsOnlyANumericDisambiguator() {
|
||||
Assert.assertEquals("GND", Node.railName("GND_1"));
|
||||
Assert.assertEquals("GND", Node.railName("GND_12"));
|
||||
Assert.assertEquals("GND", Node.railName("GND"));
|
||||
Assert.assertEquals("GND", Node.railName("GND (Pin 6)"));
|
||||
Assert.assertEquals("+5V", Node.railName("+5V_2"));
|
||||
// the annotation is stripped before the disambiguator, so an annotated rail still resolves
|
||||
Assert.assertEquals("3.3V", Node.railName("3.3V_2 (250mA)"));
|
||||
Assert.assertEquals("GND", Node.railName("GND_4 (Mid)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void railNameKeepsNonNumericQualifiers() {
|
||||
// namespaced connector signals
|
||||
Assert.assertEquals("ETH_TX-", Node.railName("ETH_TX-"));
|
||||
Assert.assertEquals("USB_D+", Node.railName("USB_D+"));
|
||||
// qualifiers that name a separate net
|
||||
Assert.assertEquals("MISO_16U2", Node.railName("MISO_16U2"));
|
||||
Assert.assertEquals("3V3_EN", Node.railName("3V3_EN"));
|
||||
// a bare trailing digit is part of the name, not a disambiguator
|
||||
Assert.assertEquals("GP1", Node.railName("GP1"));
|
||||
Assert.assertEquals("GND1", Node.railName("GND1"));
|
||||
// digits that are not at the end are part of the name too: the 16U2 bridge's ground and 5V
|
||||
// are separate nets from the board's own, and USB_5V is a connector pin, not a 5V rail
|
||||
Assert.assertEquals("GND_16U2", Node.railName("GND_16U2"));
|
||||
Assert.assertEquals("5V_16U2", Node.railName("5V_16U2"));
|
||||
Assert.assertEquals("USB_5V", Node.railName("USB_5V"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void railNameRejectsPinsThatNameNoNet() {
|
||||
Assert.assertNull(Node.railName("NC_1"));
|
||||
Assert.assertNull(Node.railName("RSV_2"));
|
||||
Assert.assertNull(Node.railName(null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class ArduinoMega extends AbstractMakerBoard {
|
||||
// Pin names in sequence (98 pins total)
|
||||
public static final String[] PIN_NAMES = new String[] {
|
||||
// Power Header (0..7)
|
||||
"NC", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN",
|
||||
"NC", "IOREF", "RESET", "3.3V", "5V", "GND_1", "GND_2", "VIN",
|
||||
// Analog Low A0..A7 (8..15)
|
||||
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7",
|
||||
// Analog High A8..A15 (16..23)
|
||||
@@ -72,7 +72,7 @@ public class ArduinoMega extends AbstractMakerBoard {
|
||||
// Digital Low D0..D7 (24..31)
|
||||
"D0 (RX0)", "D1 (TX0)", "D2 (~)", "D3 (~)", "D4 (~)", "D5 (~)", "D6 (~)", "D7 (~)",
|
||||
// Digital High D8..D13, GND, AREF, SDA, SCL (32..41)
|
||||
"D8 (~)", "D9 (~)", "D10 (~)", "D11 (~)", "D12 (~)", "D13 (~)", "GND3", "AREF", "SDA", "SCL",
|
||||
"D8 (~)", "D9 (~)", "D10 (~)", "D11 (~)", "D12 (~)", "D13 (~)", "GND_3", "AREF", "SDA", "SCL",
|
||||
// Communication Header D14..D21 (42..49)
|
||||
"D14 (TX3)", "D15 (RX3)", "D16 (TX2)", "D17 (RX2)", "D18 (TX1)", "D19 (RX1)", "D20 (SDA)", "D21 (SCL)",
|
||||
// Double Digital 2x18 Header (50..85: D22..D53, GNDx2, 5Vx2)
|
||||
|
||||
@@ -78,9 +78,9 @@ public class ArduinoNano extends AbstractMakerBoard {
|
||||
|
||||
public static final String[] PIN_NAMES = new String[] {
|
||||
// Left row (0..14, top to bottom)
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12",
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12",
|
||||
// Right row (15..29, top to bottom)
|
||||
"VIN", "GND2", "RST2", "5V", "A7", "A6", "A5", "A4", "A3", "A2", "A1", "A0", "AREF", "3.3V", "D13",
|
||||
"VIN", "GND_2", "RST2", "5V", "A7", "A6", "A5", "A4", "A3", "A2", "A1", "A0", "AREF", "3.3V", "D13",
|
||||
// ICSP (30..35)
|
||||
"MISO", "5V_ICSP", "SCK", "MOSI", "RST_ICSP", "GND_ICSP"
|
||||
};
|
||||
@@ -93,26 +93,26 @@ public class ArduinoNano extends AbstractMakerBoard {
|
||||
// Nano Every (ABX00028): PWM on D3, D5, D6, D9, D10 only, and D13 is SCK.
|
||||
public static final String[] PIN_NAMES_EVERY = new String[] {
|
||||
// Left row (0..14, top to bottom)
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (MOSI)", "D12 (MISO)",
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (MOSI)", "D12 (MISO)",
|
||||
// Right row (15..29, top to bottom)
|
||||
"VIN", "GND2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (SCK)"
|
||||
"VIN", "GND_2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (SCK)"
|
||||
};
|
||||
|
||||
// Nano 33 IoT (ABX00027): the SAMD21 adds PWM on D2 relative to the Every.
|
||||
public static final String[] PIN_NAMES_33_IOT = new String[] {
|
||||
// Left row (0..14, top to bottom)
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2 (~)", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (MOSI)", "D12 (MISO)",
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2 (~)", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (MOSI)", "D12 (MISO)",
|
||||
// Right row (15..29, top to bottom)
|
||||
"VIN", "GND2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (SCK)"
|
||||
"VIN", "GND_2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (SCK)"
|
||||
};
|
||||
|
||||
// Nano 33 BLE and BLE Sense (ABX00030): the nRF52840 routes PWM to every digital pin, D13
|
||||
// included.
|
||||
public static final String[] PIN_NAMES_33_BLE = new String[] {
|
||||
// Left row (0..14, top to bottom)
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2 (~)", "D3 (~)", "D4 (~)", "D5 (~)", "D6 (~)", "D7 (~)", "D8 (~)", "D9 (~)", "D10 (~)", "D11 (~, MOSI)", "D12 (~, MISO)",
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2 (~)", "D3 (~)", "D4 (~)", "D5 (~)", "D6 (~)", "D7 (~)", "D8 (~)", "D9 (~)", "D10 (~)", "D11 (~, MOSI)", "D12 (~, MISO)",
|
||||
// Right row (15..29, top to bottom)
|
||||
"VIN", "GND2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (~, SCK)"
|
||||
"VIN", "GND_2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (~, SCK)"
|
||||
};
|
||||
|
||||
// Nano RP2040 Connect (ABX00053): every RP2040 GPIO has a PWM slice. Note REC (BOOTSEL) where
|
||||
@@ -121,17 +121,17 @@ public class ArduinoNano extends AbstractMakerBoard {
|
||||
// Left row (0..14, top to bottom)
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2 (~)", "D3 (~)", "D4 (~)", "D5 (~)", "D6 (~)", "D7 (~)", "D8 (~)", "D9 (~)", "D10 (~)", "D11 (~, MOSI)", "D12 (~, MISO)",
|
||||
// Right row (15..29, top to bottom)
|
||||
"VIN", "GND2", "REC", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0", "AREF", "3.3V", "D13 (~, SCK)"
|
||||
"VIN", "GND_2", "REC", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0", "AREF", "3.3V", "D13 (~, SCK)"
|
||||
};
|
||||
|
||||
// Nano R4 (ABX00142): the Renesas RA4M1 board. Same PWM set as the classic, but D4 / D5 double as
|
||||
// the CAN pins, A1-A3 reach the on-chip OPAMP, A0 is the DAC, and BOOT replaces the second RESET.
|
||||
public static final String[] PIN_NAMES_R4 = new String[] {
|
||||
// Left row (0..14, top to bottom)
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2", "D3 (~)", "D4 (CAN TX)", "D5 (~, CAN RX)", "D6 (~)",
|
||||
"D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2", "D3 (~)", "D4 (CAN TX)", "D5 (~, CAN RX)", "D6 (~)",
|
||||
"D7", "D8", "D9 (~)", "D10 (~, CS)", "D11 (~, MOSI)", "D12 (MISO)",
|
||||
// Right row (15..29, top to bottom)
|
||||
"VIN", "GND2", "BOOT", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3 (OPAMP OUT)",
|
||||
"VIN", "GND_2", "BOOT", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3 (OPAMP OUT)",
|
||||
"A2 (OPAMP -)", "A1 (OPAMP +)", "A0 (DAC)", "AREF", "3.3V", "D13 (SCK)",
|
||||
// Qwiic socket (30), a single point at the centre of the connector so a wire can land on it
|
||||
"QWIIC"
|
||||
@@ -142,11 +142,11 @@ public class ArduinoNano extends AbstractMakerBoard {
|
||||
// number, which is what the ESP-IDF documentation refers to.
|
||||
public static final String[] PIN_NAMES_ESP32 = new String[] {
|
||||
// Left row (0..14, top to bottom)
|
||||
"D1 (~, TX/GPIO43)", "D0 (~, RX/GPIO44)", "RST1", "GND1", "D2 (~, GPIO5)", "D3 (~, GPIO6)",
|
||||
"D1 (~, TX/GPIO43)", "D0 (~, RX/GPIO44)", "RST1", "GND_1", "D2 (~, GPIO5)", "D3 (~, GPIO6)",
|
||||
"D4 (~, GPIO7)", "D5 (~, GPIO8)", "D6 (~, GPIO9)", "D7 (~, GPIO10)", "D8 (~, GPIO17)",
|
||||
"D9 (~, GPIO18)", "D10 (~, GPIO21)", "D11 (~, MOSI/GPIO38)", "D12 (~, MISO/GPIO47)",
|
||||
// Right row (15..29, top to bottom)
|
||||
"VIN", "GND2", "B1", "VUSB", "A7 (~, GPIO14)", "A6 (~, GPIO13)", "A5 (~, SCL/GPIO12)",
|
||||
"VIN", "GND_2", "B1", "VUSB", "A7 (~, GPIO14)", "A6 (~, GPIO13)", "A5 (~, SCL/GPIO12)",
|
||||
"A4 (~, SDA/GPIO11)", "A3 (~, GPIO4)", "A2 (~, GPIO3)", "A1 (~, GPIO2)", "A0 (~, GPIO1)",
|
||||
"B0", "3.3V", "D13 (~, SCK/GPIO48)"
|
||||
};
|
||||
|
||||
@@ -75,13 +75,13 @@ public class ArduinoUno extends AbstractMakerBoard {
|
||||
// Pin names in sequence (44 pins total)
|
||||
public static final String[] PIN_NAMES = new String[] {
|
||||
// Power Header (0..7)
|
||||
"NC", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN",
|
||||
"NC", "IOREF", "RESET", "3.3V", "5V", "GND_1", "GND_2", "VIN",
|
||||
// Analog Header (8..13)
|
||||
"A0", "A1", "A2", "A3", "A4", "A5",
|
||||
// Digital Low (14..21)
|
||||
"D0 (RX)", "D1 (TX)", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7",
|
||||
// Digital High (22..31)
|
||||
"D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND3", "AREF", "SDA", "SCL",
|
||||
"D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND_3", "AREF", "SDA", "SCL",
|
||||
// Main ICSP Header (32..37, ATmega328P)
|
||||
"MISO", "5V_ICSP", "SCK", "MOSI", "RST_ICSP", "GND_ICSP",
|
||||
// Top-Left ICSP Header (38..43, ATmega16U2)
|
||||
@@ -112,13 +112,13 @@ public class ArduinoUno extends AbstractMakerBoard {
|
||||
// ICSP header, and I2C is shared with D2 / D3 rather than living solely on A4 / A5.
|
||||
public static final String[] PIN_NAMES_LEONARDO = new String[] {
|
||||
// Power Header (0..7)
|
||||
"NC", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN",
|
||||
"NC", "IOREF", "RESET", "3.3V", "5V", "GND_1", "GND_2", "VIN",
|
||||
// Analog Header (8..13)
|
||||
"A0", "A1", "A2", "A3", "A4", "A5",
|
||||
// Digital Low (14..21)
|
||||
"D0 (RX)", "D1 (TX)", "D2 (SDA)", "D3 (~, SCL)", "D4", "D5 (~)", "D6 (~)", "D7",
|
||||
// Digital High (22..31)
|
||||
"D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND3", "AREF", "SDA", "SCL",
|
||||
"D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND_3", "AREF", "SDA", "SCL",
|
||||
// Main ICSP Header (32..37, ATmega32U4)
|
||||
"MISO", "5V_ICSP", "SCK", "MOSI", "RST_ICSP", "GND_ICSP"
|
||||
};
|
||||
@@ -141,13 +141,13 @@ public class ArduinoUno extends AbstractMakerBoard {
|
||||
// carries a 10-pin SWD debug connector in the top right corner, modelled as a single node.
|
||||
public static final String[] PIN_NAMES_R4_MINIMA = new String[] {
|
||||
// Power Header (0..7)
|
||||
"BOOT", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN",
|
||||
"BOOT", "IOREF", "RESET", "3.3V", "5V", "GND_1", "GND_2", "VIN",
|
||||
// Analog Header (8..13)
|
||||
"A0", "A1", "A2", "A3", "A4", "A5",
|
||||
// Digital Low (14..21)
|
||||
"D0 (RX)", "D1 (TX)", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7",
|
||||
// Digital High (22..31)
|
||||
"D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND3", "AREF", "SDA", "SCL",
|
||||
"D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND_3", "AREF", "SDA", "SCL",
|
||||
// Main ICSP Header (32..37, RA4M1)
|
||||
"MISO", "5V_ICSP", "SCK", "MOSI", "RST_ICSP", "GND_ICSP",
|
||||
// SWD Connector (38)
|
||||
|
||||
@@ -89,11 +89,11 @@ public class RaspberryPiPico extends AbstractMakerBoard {
|
||||
|
||||
public static final String[] PIN_NAMES = new String[] {
|
||||
// Left row (pins 0..19)
|
||||
"GP0", "GP1", "GND1", "GP2", "GP3", "GP4", "GP5", "GND2", "GP6", "GP7",
|
||||
"GP8", "GP9", "GND3", "GP10", "GP11", "GP12", "GP13", "GND4", "GP14", "GP15",
|
||||
"GP0", "GP1", "GND_1", "GP2", "GP3", "GP4", "GP5", "GND_2", "GP6", "GP7",
|
||||
"GP8", "GP9", "GND_3", "GP10", "GP11", "GP12", "GP13", "GND_4", "GP14", "GP15",
|
||||
// Right row (pins 20..39)
|
||||
"VBUS", "VSYS", "GND5", "3V3_EN", "3V3 (OUT)", "ADC_VREF", "GP28", "GND6", "GP27", "GP26",
|
||||
"RUN", "GP22", "GND7", "GP21", "GP20", "GP19", "GP18", "GND8", "GP17", "GP16",
|
||||
"VBUS", "VSYS", "GND_5", "3V3_EN", "3V3 (OUT)", "ADC_VREF", "GP28", "GND_6", "GP27", "GP26",
|
||||
"RUN", "GP22", "GND_7", "GP21", "GP20", "GP19", "GP18", "GND_8", "GP17", "GP16",
|
||||
// SWD debug pins (pins 40..42)
|
||||
"SWCLK", "GND_SWD", "SWDIO"
|
||||
};
|
||||
|
||||
@@ -89,15 +89,15 @@ public class Teensy extends AbstractMakerBoard {
|
||||
// VUSB (33, near VIN): VUSB
|
||||
public static final String[] PIN_NAMES_40 = new String[] {
|
||||
// Left row (0..13)
|
||||
"GND", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)",
|
||||
"GND_1", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)",
|
||||
"5 (IN2)", "6 (OUT1D)", "7 (RX2/OUT1A)", "8 (TX2/IN1)", "9 (OUT1C)", "10 (CS/MQSR)",
|
||||
"11 (MOSI/CTX1)", "12 (MISO/MQSL)",
|
||||
// Right row (14..27)
|
||||
"VIN (3.6-5.5V)", "GND", "3.3V (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)",
|
||||
"VIN (3.6-5.5V)", "GND_2", "3.3V_1 (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)",
|
||||
"20 (A6/TX5)", "19 (A5/SCL0)", "18 (A4/SDA0)", "17 (A3/TX4/SDA1)", "16 (A2/RX4/SCL1)",
|
||||
"15 (A1/RX3/SPDIF IN)", "14 (A0/TX3/SPDIF OUT)", "13 (SCK/CRX1/LED)",
|
||||
// End row (28..32, left to right along bottom edge)
|
||||
"VBAT", "3.3V (End)", "GND (End)", "Program", "On/Off",
|
||||
"VBAT", "3.3V_2 (End)", "GND_3 (End)", "Program", "On/Off",
|
||||
// VUSB (33)
|
||||
"VUSB"
|
||||
};
|
||||
@@ -125,19 +125,19 @@ public class Teensy extends AbstractMakerBoard {
|
||||
// VUSB (64, near VIN): VUSB
|
||||
public static final String[] PIN_NAMES_41 = new String[] {
|
||||
// Left row (0..23)
|
||||
"GND", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)",
|
||||
"GND_1", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)",
|
||||
"5 (IN2)", "6 (OUT1D)", "7 (RX2/OUT1A)", "8 (TX2/IN1)", "9 (OUT1C)", "10 (CS/MQSR)",
|
||||
"11 (MOSI/CTX1)", "12 (MISO/MQSL)", "3.3V", "24 (A10/TX6/SCL2)", "25 (A11/RX6/SDA2)",
|
||||
"11 (MOSI/CTX1)", "12 (MISO/MQSL)", "3.3V_1", "24 (A10/TX6/SCL2)", "25 (A11/RX6/SDA2)",
|
||||
"26 (A12/MOSI1)", "27 (A13/SCK1)", "28 (RX7)", "29 (TX7)", "30 (CRX3)", "31 (CTX3)",
|
||||
"32 (OUT1B)",
|
||||
// Right row (24..47)
|
||||
"VIN (3.6-5.5V)", "GND", "3.3V (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)",
|
||||
"VIN (3.6-5.5V)", "GND_2", "3.3V_2 (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)",
|
||||
"20 (A6/TX5)", "19 (A5/SCL)", "18 (A4/SDA)", "17 (A3/TX4/SDA1)", "16 (A2/RX4/SCL1)",
|
||||
"15 (A1/RX3/SPDIF IN)", "14 (A0/TX3/SPDIF OUT)", "13 (SCK/LED)", "GND", "41 (A17)",
|
||||
"15 (A1/RX3/SPDIF IN)", "14 (A0/TX3/SPDIF OUT)", "13 (SCK/LED)", "GND_3", "41 (A17)",
|
||||
"40 (A16)", "39 (A15/MISO1)", "38 (A14/CS1)", "37 (CS)", "36 (CS)", "35 (TX8)",
|
||||
"34 (RX8)", "33 (MCLK2)",
|
||||
// Middle cluster (48..52, left to right near push button)
|
||||
"VBAT", "3.3V (Mid)", "GND (Mid)", "Program", "On/Off",
|
||||
"VBAT", "3.3V_3 (Mid)", "GND_4 (Mid)", "Program", "On/Off",
|
||||
// Ethernet header (53..58, 3x2)
|
||||
"ETH_TX-", "ETH_LED", "ETH_RX+", "ETH_TX+", "ETH_GND", "ETH_RX-",
|
||||
// USB Host header (59..63, 1x5)
|
||||
@@ -160,14 +160,14 @@ public class Teensy extends AbstractMakerBoard {
|
||||
// severable VUSB pad next to Vin. Labels follow the PJRC Teensy 3.2 pinout card.
|
||||
public static final String[] PIN_NAMES_32 = new String[] {
|
||||
// Left row (0..13)
|
||||
"GND", "0 (RX1)", "1 (TX1)", "2", "3 (PWM)", "4 (PWM)", "5 (PWM)", "6 (PWM)", "7 (RX3)",
|
||||
"GND_1", "0 (RX1)", "1 (TX1)", "2", "3 (PWM)", "4 (PWM)", "5 (PWM)", "6 (PWM)", "7 (RX3)",
|
||||
"8 (TX3)", "9 (RX2/PWM)", "10 (TX2/CS/PWM)", "11 (MOSI)", "12 (MISO)",
|
||||
// Right row (14..27)
|
||||
"Vin (3.6-6.0V)", "AGND", "3.3V (100mA)", "23 (A9/PWM)", "22 (A8/PWM)", "21 (A7/PWM)",
|
||||
"Vin (3.6-6.0V)", "AGND", "3.3V_1 (100mA)", "23 (A9/PWM)", "22 (A8/PWM)", "21 (A7/PWM)",
|
||||
"20 (A6/PWM)", "19 (A5/SCL0)", "18 (A4/SDA0)", "17 (A3)", "16 (A2)", "15 (A1/CS)",
|
||||
"14 (A0)", "13 (SCK/LED)",
|
||||
// End row (28..32, left to right along bottom edge)
|
||||
"VBAT", "3.3V (End)", "GND (End)", "Program", "A14/DAC",
|
||||
"VBAT", "3.3V_2 (End)", "GND_2 (End)", "Program", "A14/DAC",
|
||||
// VUSB (33)
|
||||
"VUSB"
|
||||
};
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ public class Jumbo4PinTubeSocket extends AbstractAngledComponent<String> {
|
||||
public static Size PIN_DIAMETER = new Size(1d, SizeUnit.mm);
|
||||
public static Size HOLE_SIZE = new Size(5d, SizeUnit.mm);
|
||||
public static Size OCTAL_TICK_SIZE = new Size(2d, SizeUnit.mm);
|
||||
private static String[] electrodeLabels = new String[] {"G", "F", "P", "F"};
|
||||
private static String[] electrodeLabels = new String[] {"G", "F1", "P", "F2"};
|
||||
|
||||
private String type = "";
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
*
|
||||
* 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.maker;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.netlist.Node;
|
||||
import org.diylc.components.displays.WS2812BStrip;
|
||||
import org.diylc.components.micro.ArduinoUno;
|
||||
import org.diylc.components.micro.ESP32DevKit;
|
||||
import org.diylc.components.micro.ESP8266NodeMCU;
|
||||
import org.diylc.components.micro.RaspberryPi;
|
||||
import org.diylc.components.micro.RaspberryPiPico;
|
||||
import org.diylc.components.micro.Teensy;
|
||||
|
||||
/**
|
||||
* Pins a board joins inside itself, as the netlist decides them through
|
||||
* {@link Node#railName(String)}.
|
||||
* <p>
|
||||
* The negative cases carry the weight here. An underscore does three unrelated jobs across these
|
||||
* pinouts - it disambiguates a repeated rail, it namespaces a connector's signals, and it qualifies
|
||||
* a separate net - so a rule keyed on the text before the underscore would short an Ethernet or USB
|
||||
* header together. Each of these was produced by a sweep of every board and variant.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class BoardRailTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
try {
|
||||
ConfigurationManager.getInstance().initialize("diylc");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repeatedRailsAreOneNode() throws Exception {
|
||||
ESP32DevKit esp = new ESP32DevKit();
|
||||
assertJoined(esp, "GND_1", "GND_2");
|
||||
|
||||
RaspberryPi pi = new RaspberryPi();
|
||||
assertJoined(pi, "GND (Pin 6)", "GND (Pin 9)");
|
||||
assertJoined(pi, "5V (Pin 2)", "5V (Pin 4)");
|
||||
assertJoined(pi, "3.3V (Pin 1)", "3.3V (Pin 17)");
|
||||
|
||||
RaspberryPiPico pico = new RaspberryPiPico();
|
||||
assertJoined(pico, "GND_1", "GND_8");
|
||||
|
||||
WS2812BStrip strip = new WS2812BStrip();
|
||||
assertJoined(strip, "GND_1", "GND_2");
|
||||
assertJoined(strip, "+5V_1", "+5V_2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void annotatedRailsAreOneNode() throws Exception {
|
||||
Teensy teensy = new Teensy();
|
||||
teensy.setVersion(Teensy.TeensyVersion.Teensy_4_1);
|
||||
assertJoined(teensy, "GND_1", "GND_4 (Mid)");
|
||||
assertJoined(teensy, "3.3V_1", "3.3V_2 (250mA)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectorSignalsSharingAPrefixAreNotJoined() throws Exception {
|
||||
Teensy teensy = new Teensy();
|
||||
teensy.setVersion(Teensy.TeensyVersion.Teensy_4_1);
|
||||
assertSeparate(teensy, "ETH_TX-", "ETH_RX+");
|
||||
assertSeparate(teensy, "ETH_TX-", "ETH_GND");
|
||||
assertSeparate(teensy, "USB_D-", "USB_D+");
|
||||
assertSeparate(teensy, "USB_5V", "USB_GND1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void qualifiedNamesAreSeparateNets() throws Exception {
|
||||
ArduinoUno uno = new ArduinoUno();
|
||||
uno.setVersion(ArduinoUno.ArduinoUnoVersion.REV3);
|
||||
// the USB bridge has its own ICSP header; it is not the main MCU's SPI
|
||||
assertSeparate(uno, "MISO", "MISO_16U2");
|
||||
assertSeparate(uno, "SCK", "SCK_16U2");
|
||||
|
||||
RaspberryPiPico pico = new RaspberryPiPico();
|
||||
// the regulator's enable input is not its output
|
||||
assertSeparate(pico, "3V3_EN", "3V3 (OUT)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void absentConnectionsAreNotANet() throws Exception {
|
||||
ESP32DevKit esp = new ESP32DevKit();
|
||||
esp.setVersion(ESP32DevKit.DevKitVersion.ESP32_C6_DevKitC_1);
|
||||
assertSeparate(esp, "NC_1", "NC_2");
|
||||
|
||||
ESP8266NodeMCU nodeMcu = new ESP8266NodeMCU();
|
||||
assertSeparate(nodeMcu, "RSV_1", "RSV_2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signalPinsAreNeverJoined() throws Exception {
|
||||
RaspberryPiPico pico = new RaspberryPiPico();
|
||||
assertSeparate(pico, "GP0", "GP1");
|
||||
assertSeparate(pico, "GP0", "GP28");
|
||||
}
|
||||
|
||||
private static void assertJoined(IDIYComponent<?> board, String first, String second) {
|
||||
Assert.assertTrue(board.getClass().getSimpleName() + ": \"" + first + "\" and \"" + second
|
||||
+ "\" should be one node",
|
||||
sameRail(board, indexOf(board, first), indexOf(board, second)));
|
||||
}
|
||||
|
||||
private static void assertSeparate(IDIYComponent<?> board, String first, String second) {
|
||||
Assert.assertFalse(board.getClass().getSimpleName() + ": \"" + first + "\" and \"" + second
|
||||
+ "\" should stay separate nodes",
|
||||
sameRail(board, indexOf(board, first), indexOf(board, second)));
|
||||
}
|
||||
|
||||
private static boolean sameRail(IDIYComponent<?> board, int index1, int index2) {
|
||||
String rail = Node.railName(board.getControlPointNodeName(index1));
|
||||
return rail != null && rail.equals(Node.railName(board.getControlPointNodeName(index2)));
|
||||
}
|
||||
|
||||
private static int indexOf(IDIYComponent<?> board, String nodeName) {
|
||||
for (int i = 0; i < board.getControlPointCount(); i++) {
|
||||
if (nodeName.equals(board.getControlPointNodeName(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
board.getClass().getSimpleName() + " has no pin named \"" + nodeName + "\"");
|
||||
}
|
||||
}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
*
|
||||
* 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.maker;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.PropertyWrapper;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.presenter.ComponentProcessor;
|
||||
|
||||
/**
|
||||
* Sweeps every registered maker component across the cross product of its enum properties.
|
||||
* <p>
|
||||
* The per-class tests pin down the figures each variant was built from, and
|
||||
* {@link MakerComponentsTest} covers discovery, icons and rotation. Both of those exercise the
|
||||
* default variant only: a component is built with its no-argument constructor and never asked for
|
||||
* another version. The variants are where the geometry actually differs, and a release makes
|
||||
* control-point counts and positions permanent, so this walks all of them.
|
||||
* <p>
|
||||
* The properties are swept as a cross product rather than one at a time because a variant is
|
||||
* usually a pair - a panel size and an interface, a controller and a glass - and only the
|
||||
* combination decides the pin count.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class MakerVariantSweepTest {
|
||||
|
||||
private static final String[] PACKAGES =
|
||||
{"org.diylc.components.displays", "org.diylc.components.micro"};
|
||||
|
||||
/**
|
||||
* Orientation is swept inside the drawing check instead of joining the cross product: it moves
|
||||
* the control points but cannot change how many there are or what they are called, so folding it
|
||||
* in would only report the same defect once per rotation.
|
||||
*/
|
||||
private static final String ORIENTATION = "Orientation";
|
||||
|
||||
private static final Project PROJECT = new Project();
|
||||
|
||||
private static final IDrawingObserver OBSERVER = new IDrawingObserver() {
|
||||
|
||||
@Override
|
||||
public void stopTracking() {}
|
||||
|
||||
@Override
|
||||
public void startTracking() {}
|
||||
|
||||
@Override
|
||||
public void startTrackingContinuityArea(boolean positive) {}
|
||||
|
||||
@Override
|
||||
public void stopTrackingContinuityArea() {}
|
||||
|
||||
@Override
|
||||
public boolean isTrackingContinuityArea() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuityMarker(String marker) {}
|
||||
};
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
try {
|
||||
ConfigurationManager.getInstance().initialize("diylc");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Problems are collected rather than asserted one at a time, so a run reports every bad variant
|
||||
* at once instead of stopping at the first.
|
||||
*/
|
||||
@Test
|
||||
public void everyVariantHasSoundControlPointsAndDraws() throws Exception {
|
||||
List<String> problems = new ArrayList<String>();
|
||||
int swept = 0;
|
||||
|
||||
for (Class<? extends IDIYComponent<?>> clazz : makerComponentClasses()) {
|
||||
List<PropertyWrapper> enums = new ArrayList<PropertyWrapper>();
|
||||
PropertyWrapper orientation = null;
|
||||
for (PropertyWrapper property : ComponentProcessor.getInstance().extractProperties(clazz)) {
|
||||
if (!property.getType().isEnum()) {
|
||||
continue;
|
||||
}
|
||||
if (ORIENTATION.equals(property.getName())) {
|
||||
orientation = property;
|
||||
} else {
|
||||
enums.add(property);
|
||||
}
|
||||
}
|
||||
|
||||
int combinations = 1;
|
||||
for (PropertyWrapper property : enums) {
|
||||
combinations *= property.getType().getEnumConstants().length;
|
||||
}
|
||||
|
||||
for (int index = 0; index < combinations; index++) {
|
||||
IDIYComponent<?> component = clazz.getDeclaredConstructor().newInstance();
|
||||
StringBuilder where = new StringBuilder(clazz.getSimpleName());
|
||||
int remaining = index;
|
||||
boolean complete = true;
|
||||
|
||||
for (PropertyWrapper property : enums) {
|
||||
Object[] values = property.getType().getEnumConstants();
|
||||
Object value = values[remaining % values.length];
|
||||
remaining /= values.length;
|
||||
where.append(" / ").append(property.getName()).append("=").append(value);
|
||||
|
||||
PropertyWrapper applied = (PropertyWrapper) property.clone();
|
||||
applied.setValue(value);
|
||||
try {
|
||||
applied.writeTo(component);
|
||||
} catch (Exception e) {
|
||||
problems.add(where + ": will not apply -- " + e.getClass().getSimpleName());
|
||||
complete = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!complete) {
|
||||
continue;
|
||||
}
|
||||
swept++;
|
||||
|
||||
checkNodeNamesAreUnique(component, where.toString(), problems);
|
||||
checkControlPointsAreDistinct(component, where.toString(), problems);
|
||||
checkDrawsInEveryOrientation(component, orientation, where.toString(), problems);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue("No maker components were discovered", swept > 0);
|
||||
Assert.assertTrue(swept + " variants swept, " + problems.size() + " problem(s):\n"
|
||||
+ String.join("\n", problems), problems.isEmpty());
|
||||
}
|
||||
|
||||
private static void checkNodeNamesAreUnique(IDIYComponent<?> component, String where,
|
||||
List<String> problems) {
|
||||
Set<String> seen = new HashSet<String>();
|
||||
for (int i = 0; i < component.getControlPointCount(); i++) {
|
||||
String name = component.getControlPointNodeName(i);
|
||||
// A null name is a point that carries no node, which is not a collision.
|
||||
if (name != null && !seen.add(name)) {
|
||||
problems.add(where + ": duplicate node name \"" + name + "\" at point " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkControlPointsAreDistinct(IDIYComponent<?> component, String where,
|
||||
List<String> problems) {
|
||||
int count = component.getControlPointCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Point2D first = component.getControlPoint(i);
|
||||
for (int j = i + 1; j < count; j++) {
|
||||
if (first.distance(component.getControlPoint(j)) <= 0.001) {
|
||||
problems.add(where + ": control points " + i + " and " + j + " coincide");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDrawsInEveryOrientation(IDIYComponent<?> component,
|
||||
PropertyWrapper orientation, String where, List<String> problems)
|
||||
throws CloneNotSupportedException {
|
||||
Object[] orientations =
|
||||
orientation == null ? new Object[] {null} : orientation.getType().getEnumConstants();
|
||||
|
||||
for (Object value : orientations) {
|
||||
String at = where;
|
||||
if (value != null) {
|
||||
at = where + " / " + orientation.getName() + "=" + value;
|
||||
PropertyWrapper applied = (PropertyWrapper) orientation.clone();
|
||||
applied.setValue(value);
|
||||
try {
|
||||
applied.writeTo(component);
|
||||
} catch (Exception e) {
|
||||
problems.add(at + ": will not apply -- " + e.getClass().getSimpleName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
BufferedImage image = new BufferedImage(600, 600, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
// Translating the canvas rather than moving control point 0 keeps the component's own
|
||||
// geometry intact: a drag sets every control point, so moving only the first leaves the rest
|
||||
// stale and the component draws from a mix of old and new positions.
|
||||
g2d.translate(300, 300);
|
||||
try {
|
||||
component.draw(g2d, ComponentState.NORMAL, false, PROJECT, OBSERVER);
|
||||
component.draw(g2d, ComponentState.SELECTED, false, PROJECT, OBSERVER);
|
||||
component.draw(g2d, ComponentState.NORMAL, true, PROJECT, OBSERVER);
|
||||
component.drawIcon(g2d, 32, 32);
|
||||
} catch (Exception e) {
|
||||
problems.add(at + ": draw threw " + e.getClass().getSimpleName() + " -- " + e.getMessage());
|
||||
} finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Class<? extends IDIYComponent<?>>> makerComponentClasses() {
|
||||
List<Class<? extends IDIYComponent<?>>> classes =
|
||||
new ArrayList<Class<? extends IDIYComponent<?>>>();
|
||||
for (List<ComponentType> category : ComponentProcessor.getInstance().getComponentTypes()
|
||||
.values()) {
|
||||
for (ComponentType type : category) {
|
||||
String name = type.getInstanceClass().getName();
|
||||
for (String prefix : PACKAGES) {
|
||||
if (name.startsWith(prefix + ".")) {
|
||||
classes.add(type.getInstanceClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Discovery order is not stable, and a failure message naming variants reads better sorted.
|
||||
Collections.sort(classes, new Comparator<Class<? extends IDIYComponent<?>>>() {
|
||||
|
||||
@Override
|
||||
public int compare(Class<? extends IDIYComponent<?>> a, Class<? extends IDIYComponent<?>> b) {
|
||||
return a.getName().compareTo(b.getName());
|
||||
}
|
||||
});
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,8 @@ public class ArduinoNanoTest {
|
||||
Assert.assertEquals("MISO", classic.getControlPointNodeName(30));
|
||||
|
||||
// the silkscreen prints what is on the board: both grounds and both resets are printed alike
|
||||
Assert.assertEquals("GND1", classic.getControlPointNodeName(3));
|
||||
Assert.assertEquals("GND2", classic.getControlPointNodeName(16));
|
||||
Assert.assertEquals("GND_1", classic.getControlPointNodeName(3));
|
||||
Assert.assertEquals("GND_2", classic.getControlPointNodeName(16));
|
||||
Assert.assertEquals("GND", classic.getSilkPinLabel(3));
|
||||
Assert.assertEquals("GND", classic.getSilkPinLabel(16));
|
||||
Assert.assertEquals("RST", classic.getSilkPinLabel(2));
|
||||
|
||||
@@ -55,8 +55,8 @@ public class ArduinoUnoTest {
|
||||
|
||||
// the silkscreen prints what is on the board rather than the node name: both grounds of the
|
||||
// power header say "GND" and the reserved first pin says nothing at all
|
||||
Assert.assertEquals("GND1", r3.getControlPointNodeName(5));
|
||||
Assert.assertEquals("GND2", r3.getControlPointNodeName(6));
|
||||
Assert.assertEquals("GND_1", r3.getControlPointNodeName(5));
|
||||
Assert.assertEquals("GND_2", r3.getControlPointNodeName(6));
|
||||
Assert.assertEquals("GND", r3.getSilkPinLabel(5));
|
||||
Assert.assertEquals("GND", r3.getSilkPinLabel(6));
|
||||
Assert.assertEquals("", r3.getSilkPinLabel(0));
|
||||
|
||||
@@ -36,7 +36,7 @@ public class TeensyTest {
|
||||
Teensy teensy40 = new Teensy();
|
||||
Assert.assertEquals(TeensyVersion.Teensy_4_0, teensy40.getVersion());
|
||||
Assert.assertEquals(34, teensy40.getControlPointCount());
|
||||
Assert.assertEquals("GND", teensy40.getControlPointNodeName(0));
|
||||
Assert.assertEquals("GND_1", teensy40.getControlPointNodeName(0));
|
||||
Assert.assertEquals("0 (RX1/CS1/CRX2)", teensy40.getControlPointNodeName(1));
|
||||
Assert.assertEquals("12 (MISO/MQSL)", teensy40.getControlPointNodeName(13));
|
||||
Assert.assertEquals("VIN (3.6-5.5V)", teensy40.getControlPointNodeName(14));
|
||||
|
||||
@@ -127,8 +127,11 @@ What these tests are for, beyond the obvious count-and-dimension checks:
|
||||
clears the mounting holes; no hole is drilled where a pad is; a lit area that is measured really
|
||||
does disagree with the rule it replaced. A test that restates the constant passes while the
|
||||
component is wrong.
|
||||
- **No duplicate node names,** on every variant. Duplicates reach the netlist as one node and are
|
||||
invisible on the canvas.
|
||||
- **No duplicate node names,** on every variant. The cost is legibility, not connectivity: nodes are
|
||||
identified by component and control point index and the netlist graph is keyed on position, so two
|
||||
pads named `GND` stay two nodes that merely print the same, and a reader cannot tell which one a
|
||||
connection reached. They are invisible on the canvas too, since `getDisplayPinLabel` cuts the
|
||||
suffix off and both silk as `GND`. `Teensy` is the one board that still repeats a bare name.
|
||||
- **A drawing smoke test** over every variant, state and orientation.
|
||||
- When a refactor is meant to change nothing, **render before and after and compare pixels.** The
|
||||
`digitCount` work was verified that way, and it caught a sub-pixel hole shift the unit tests
|
||||
|
||||
Reference in New Issue
Block a user