mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd9ac32a2f | |||
| 9922615de5 | |||
| 44b57f4880 | |||
| 486e83b6b5 | |||
| eb48ab9664 | |||
| a743de4024 | |||
| f62b3ddf04 | |||
| f77c821f0a | |||
| 208c157016 | |||
| c6d4b9df9c |
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>6.6.0</version>
|
||||
<version>6.7.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-core</artifactId>
|
||||
@@ -60,7 +60,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.22.1</version>
|
||||
<version>2.22.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
@@ -4897,7 +4897,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.CharacterLCD_-LCDInterface>I2C_Backpack</diylc.displays.CharacterLCD_-LCDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>16x2 Parallel HD44780</name>
|
||||
@@ -4906,7 +4906,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.CharacterLCD_-LCDInterface>Parallel_16Pin</diylc.displays.CharacterLCD_-LCDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>20x4 I2C Backpack</name>
|
||||
@@ -4915,7 +4915,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.CharacterLCD_-LCDInterface>I2C_Backpack</diylc.displays.CharacterLCD_-LCDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>20x4 Parallel HD44780</name>
|
||||
@@ -4924,7 +4924,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.CharacterLCD_-LCDInterface>Parallel_16Pin</diylc.displays.CharacterLCD_-LCDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
@@ -4938,7 +4938,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>I2C_4Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>0.96" SSD1306 SPI (7-Pin)</name>
|
||||
@@ -4947,7 +4947,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_7Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>0.96" SSD1306 SPI (6-Pin)</name>
|
||||
@@ -4956,7 +4956,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_6Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>0.96" SSD1306 SPI (8-Pin)</name>
|
||||
@@ -4965,7 +4965,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_8Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>0.91" SSD1306 I2C</name>
|
||||
@@ -4974,7 +4974,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>I2C_4Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>0.91" SSD1306 SPI (7-Pin)</name>
|
||||
@@ -4983,7 +4983,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_7Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>0.91" SSD1306 SPI (6-Pin)</name>
|
||||
@@ -4992,7 +4992,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_6Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>0.91" SSD1306 SPI (8-Pin)</name>
|
||||
@@ -5001,7 +5001,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_8Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>1.3" SH1106 I2C</name>
|
||||
@@ -5010,7 +5010,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>I2C_4Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>1.3" SH1106 SPI (7-Pin)</name>
|
||||
@@ -5019,7 +5019,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_7Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>1.3" SH1106 SPI (6-Pin)</name>
|
||||
@@ -5028,7 +5028,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_6Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>1.3" SH1106 SPI (8-Pin)</name>
|
||||
@@ -5037,7 +5037,7 @@
|
||||
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_8Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
@@ -5050,7 +5050,7 @@
|
||||
<entry><string>Controller</string><diylc.displays.TFTDisplay_-Controller>ILI9341_2_8</diylc.displays.TFTDisplay_-Controller></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>2.4" ILI9341 (Touch + SD)</name>
|
||||
@@ -5058,7 +5058,7 @@
|
||||
<entry><string>Controller</string><diylc.displays.TFTDisplay_-Controller>ILI9341_2_4</diylc.displays.TFTDisplay_-Controller></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>0.96" ST7735 80x160</name>
|
||||
@@ -5066,7 +5066,7 @@
|
||||
<entry><string>Controller</string><diylc.displays.TFTDisplay_-Controller>ST7735_0_96</diylc.displays.TFTDisplay_-Controller></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>1.8" ST7735 128x160</name>
|
||||
@@ -5074,7 +5074,7 @@
|
||||
<entry><string>Controller</string><diylc.displays.TFTDisplay_-Controller>ST7735_1_8</diylc.displays.TFTDisplay_-Controller></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>1.8" ST7735 128x160 (SD)</name>
|
||||
@@ -5082,7 +5082,7 @@
|
||||
<entry><string>Controller</string><diylc.displays.TFTDisplay_-Controller>ST7735_1_8_SD</diylc.displays.TFTDisplay_-Controller></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>1.54" ST7789 240x240</name>
|
||||
@@ -5090,7 +5090,7 @@
|
||||
<entry><string>Controller</string><diylc.displays.TFTDisplay_-Controller>ST7789_1_54</diylc.displays.TFTDisplay_-Controller></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>1.28" GC9A01 Round</name>
|
||||
@@ -5098,7 +5098,7 @@
|
||||
<entry><string>Controller</string><diylc.displays.TFTDisplay_-Controller>GC9A01_1_28</diylc.displays.TFTDisplay_-Controller></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
@@ -5112,7 +5112,7 @@
|
||||
<entry><string>Common</string><diylc.displays.SevenSegmentDisplay_-Common>Cathode</diylc.displays.SevenSegmentDisplay_-Common></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>1-Digit 0.56" Common Anode</name>
|
||||
@@ -5121,7 +5121,7 @@
|
||||
<entry><string>Common</string><diylc.displays.SevenSegmentDisplay_-Common>Anode</diylc.displays.SevenSegmentDisplay_-Common></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>2-Digit 0.56" Common Cathode</name>
|
||||
@@ -5130,7 +5130,7 @@
|
||||
<entry><string>Common</string><diylc.displays.SevenSegmentDisplay_-Common>Cathode</diylc.displays.SevenSegmentDisplay_-Common></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>2-Digit 0.56" Common Anode</name>
|
||||
@@ -5139,7 +5139,7 @@
|
||||
<entry><string>Common</string><diylc.displays.SevenSegmentDisplay_-Common>Anode</diylc.displays.SevenSegmentDisplay_-Common></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>4-Digit 0.36" Common Cathode</name>
|
||||
@@ -5148,7 +5148,7 @@
|
||||
<entry><string>Common</string><diylc.displays.SevenSegmentDisplay_-Common>Cathode</diylc.displays.SevenSegmentDisplay_-Common></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>4-Digit 0.36" Common Anode</name>
|
||||
@@ -5157,7 +5157,7 @@
|
||||
<entry><string>Common</string><diylc.displays.SevenSegmentDisplay_-Common>Anode</diylc.displays.SevenSegmentDisplay_-Common></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>4-Digit 0.56" Common Cathode</name>
|
||||
@@ -5166,7 +5166,7 @@
|
||||
<entry><string>Common</string><diylc.displays.SevenSegmentDisplay_-Common>Cathode</diylc.displays.SevenSegmentDisplay_-Common></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>4-Digit 0.56" Common Anode</name>
|
||||
@@ -5175,7 +5175,7 @@
|
||||
<entry><string>Common</string><diylc.displays.SevenSegmentDisplay_-Common>Anode</diylc.displays.SevenSegmentDisplay_-Common></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>4-Digit TM1637 Module</name>
|
||||
@@ -5183,7 +5183,7 @@
|
||||
<entry><string>Type</string><diylc.displays.SevenSegmentDisplay_-DisplayType>TM1637_Module_4Pin</diylc.displays.SevenSegmentDisplay_-DisplayType></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
@@ -5196,7 +5196,7 @@
|
||||
<entry><string>Modules</string><diylc.displays.LEDMatrix_-Modules>Single_8x8</diylc.displays.LEDMatrix_-Modules></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>Compact 8x8 (MAX7219)</name>
|
||||
@@ -5204,7 +5204,7 @@
|
||||
<entry><string>Modules</string><diylc.displays.LEDMatrix_-Modules>Compact_8x8</diylc.displays.LEDMatrix_-Modules></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>4-in-1 32x8 (MAX7219)</name>
|
||||
@@ -5212,7 +5212,7 @@
|
||||
<entry><string>Modules</string><diylc.displays.LEDMatrix_-Modules>FourInOne_32x8</diylc.displays.LEDMatrix_-Modules></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>8-in-1 64x8 (MAX7219)</name>
|
||||
@@ -5220,7 +5220,7 @@
|
||||
<entry><string>Modules</string><diylc.displays.LEDMatrix_-Modules>EightInOne_64x8</diylc.displays.LEDMatrix_-Modules></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
@@ -5233,7 +5233,7 @@
|
||||
<entry><string>Segments</string><diylc.displays.LEDBarGraph_-Segments>_4</diylc.displays.LEDBarGraph_-Segments></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>5 Segment</name>
|
||||
@@ -5241,7 +5241,7 @@
|
||||
<entry><string>Segments</string><diylc.displays.LEDBarGraph_-Segments>_5</diylc.displays.LEDBarGraph_-Segments></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>6 Segment</name>
|
||||
@@ -5249,7 +5249,7 @@
|
||||
<entry><string>Segments</string><diylc.displays.LEDBarGraph_-Segments>_6</diylc.displays.LEDBarGraph_-Segments></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>8 Segment</name>
|
||||
@@ -5257,7 +5257,7 @@
|
||||
<entry><string>Segments</string><diylc.displays.LEDBarGraph_-Segments>_8</diylc.displays.LEDBarGraph_-Segments></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>10 Segment</name>
|
||||
@@ -5265,7 +5265,7 @@
|
||||
<entry><string>Segments</string><diylc.displays.LEDBarGraph_-Segments>_10</diylc.displays.LEDBarGraph_-Segments></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>12 Segment</name>
|
||||
@@ -5273,7 +5273,7 @@
|
||||
<entry><string>Segments</string><diylc.displays.LEDBarGraph_-Segments>_12</diylc.displays.LEDBarGraph_-Segments></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
@@ -5286,7 +5286,7 @@
|
||||
<entry><string>Ring Size</string><diylc.displays.WS2812BRing_-RingSize>_12_LED</diylc.displays.WS2812BRing_-RingSize></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>16 LEDs</name>
|
||||
@@ -5294,7 +5294,7 @@
|
||||
<entry><string>Ring Size</string><diylc.displays.WS2812BRing_-RingSize>_16_LED</diylc.displays.WS2812BRing_-RingSize></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>24 LEDs</name>
|
||||
@@ -5302,7 +5302,7 @@
|
||||
<entry><string>Ring Size</string><diylc.displays.WS2812BRing_-RingSize>_24_LED</diylc.displays.WS2812BRing_-RingSize></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
@@ -5315,7 +5315,7 @@
|
||||
<entry><string>Density</string><diylc.displays.WS2812BStrip_-Density>_30</diylc.displays.WS2812BStrip_-Density></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>60 LED/m</name>
|
||||
@@ -5323,7 +5323,7 @@
|
||||
<entry><string>Density</string><diylc.displays.WS2812BStrip_-Density>_60</diylc.displays.WS2812BStrip_-Density></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>144 LED/m</name>
|
||||
@@ -5331,7 +5331,7 @@
|
||||
<entry><string>Density</string><diylc.displays.WS2812BStrip_-Density>_144</diylc.displays.WS2812BStrip_-Density></entry>
|
||||
</values>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
|
||||
<createdOn>2026-10-09T00:00:00.0000000</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
|
||||
@@ -502,7 +502,7 @@
|
||||
<minor>7</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2026-10-11 00:00:00.000 CET</releaseDate>
|
||||
<releaseDate>2026-10-09 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>6.6.0</version>
|
||||
<version>6.7.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-library</artifactId>
|
||||
|
||||
@@ -343,7 +343,7 @@ public class MakerBoardPainter {
|
||||
* nothing. The variant string reads "size, interface", and breaking it at the comma gives one
|
||||
* property per line, which keeps the longest line well inside the glass instead of spanning it.
|
||||
*/
|
||||
private static String screenText(Display display, String name, String value) {
|
||||
public static String screenText(Display display, String name, String value) {
|
||||
boolean hasValue = value != null && !value.trim().isEmpty();
|
||||
String variant = hasValue ? value.replace(", ", "\n") : null;
|
||||
|
||||
|
||||
+110
-33
@@ -30,6 +30,7 @@ import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.util.List;
|
||||
|
||||
import org.diylc.awt.StringUtils;
|
||||
import org.diylc.common.Display;
|
||||
@@ -62,8 +63,6 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Size HEADER_OFFSET = new Size(2.54d, SizeUnit.mm);
|
||||
// The parallel row is not centred: its leftmost pin starts this far in from the left edge.
|
||||
public static Size PARALLEL_HEADER_INSET = new Size(10.0d, SizeUnit.mm);
|
||||
// The I2C backpack's four pins stand in a column against the left edge, centred on the height.
|
||||
public static Size I2C_HEADER_INSET = new Size(2.5d, SizeUnit.mm);
|
||||
// A 3 mm hole centred 2.5 mm in leaves only 1 mm of board outside it, which is what the part
|
||||
@@ -71,6 +70,15 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
public static Size MOUNTING_HOLE_INSET = new Size(2.5d, SizeUnit.mm);
|
||||
public static Size MOUNTING_HOLE_SIZE = new Size(3.0d, SizeUnit.mm);
|
||||
|
||||
// The HD44780 character box. Square dots on a 0.6 mm pitch make the cell 2.95 x 4.75 mm, the
|
||||
// figure the datasheet quotes, so the cell is derived from the pitch rather than stored twice.
|
||||
public static Size DOT_SIZE = new Size(0.55d, SizeUnit.mm);
|
||||
public static Size DOT_PITCH = new Size(0.6d, SizeUnit.mm);
|
||||
public static Size CHAR_PITCH_X = new Size(3.55d, SizeUnit.mm);
|
||||
public static Size CHAR_PITCH_Y = new Size(5.35d, SizeUnit.mm);
|
||||
public static final int DOT_COLUMNS = 5;
|
||||
public static final int DOT_ROWS = 8;
|
||||
|
||||
public static Color PCB_GREEN = Color.decode("#1B5E20");
|
||||
public static Color SCREEN_BG = Color.decode("#1E88E5");
|
||||
public static Color BEZEL_COLOR = Color.decode("#212121");
|
||||
@@ -80,17 +88,13 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
public static Color SCREEN_INK_DARK = Color.decode("#1B2631");
|
||||
|
||||
public static final String[] PIN_NAMES_I2C = new String[] {"GND", "VCC", "SDA", "SCL"};
|
||||
// The bare designation the module prints, with the function in parentheses where the pin's name
|
||||
// does not give it away. The silkscreen label is the part before the parenthesis, there being no
|
||||
// room for more along a 0.1" pitch.
|
||||
public static final String[] PIN_NAMES_PARALLEL = new String[] {
|
||||
"VSS (GND)", "VCC (+5V)", "VEE (Contrast)", "RS", "RW", "E",
|
||||
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
|
||||
"A (Backlight +)", "K (Backlight -)"
|
||||
};
|
||||
|
||||
// The bare designations the module prints. The supply and backlight pins carry their function
|
||||
// in the node name instead, there being no room for it along a 0.1" pitch.
|
||||
public static final String[] SILK_NAMES_PARALLEL = new String[] {
|
||||
"VSS", "VCC", "VEE", "RS", "RW", "E",
|
||||
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "A", "K"
|
||||
"VSS (GND)", "VDD (+5V)", "VO (Contrast)", "RS", "R/W", "E",
|
||||
"DB0", "DB1", "DB2", "DB3", "DB4", "DB5", "DB6", "DB7",
|
||||
"LEDA (Backlight +)", "LEDK (Backlight -)"
|
||||
};
|
||||
|
||||
private LCDSize lcdSize = LCDSize._16x2;
|
||||
@@ -166,15 +170,6 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
return "Pin " + (index + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSilkPinLabel(int index) {
|
||||
if (lcdInterface == LCDInterface.Parallel_16Pin && index >= 0
|
||||
&& index < SILK_NAMES_PARALLEL.length) {
|
||||
return SILK_NAMES_PARALLEL[index];
|
||||
}
|
||||
return super.getSilkPinLabel(index);
|
||||
}
|
||||
|
||||
private int getPinCount() {
|
||||
return lcdInterface == LCDInterface.I2C_Backpack ? PIN_NAMES_I2C.length
|
||||
: PIN_NAMES_PARALLEL.length;
|
||||
@@ -185,6 +180,14 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
return CalcUtils.calculateLuminance(screenColor) < 128d ? SCREEN_INK_LIGHT : SCREEN_INK_DARK;
|
||||
}
|
||||
|
||||
/**
|
||||
* An unlit dot is the backlight seen through the cell, so it shows as a shade of the backlight
|
||||
* rather than a colour of its own and follows whatever the user picks.
|
||||
*/
|
||||
private Color getDotColor() {
|
||||
return screenColor.darker();
|
||||
}
|
||||
|
||||
private double getBoardWidth() {
|
||||
return new Size(lcdSize.getWidthMm(), SizeUnit.mm).convertToPixels();
|
||||
}
|
||||
@@ -194,12 +197,12 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Neither header is centred: the parallel row starts a fixed distance in from the left, and the
|
||||
* I2C column stands against that same edge.
|
||||
* Neither header is centred: the parallel row starts a distance in from the left that the module
|
||||
* itself dictates, and the I2C column stands against that same edge whatever the module.
|
||||
*/
|
||||
private double getBoardX(double x) {
|
||||
Size inset =
|
||||
lcdInterface == LCDInterface.I2C_Backpack ? I2C_HEADER_INSET : PARALLEL_HEADER_INSET;
|
||||
Size inset = lcdInterface == LCDInterface.I2C_Backpack ? I2C_HEADER_INSET
|
||||
: new Size(lcdSize.getParallelHeaderInsetMm(), SizeUnit.mm);
|
||||
return x - inset.convertToPixels();
|
||||
}
|
||||
|
||||
@@ -302,9 +305,7 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
g2d.setColor(screenColor);
|
||||
g2d.fill(new Rectangle2D.Double(screenX, screenY, screenW, screenH));
|
||||
|
||||
MakerBoardPainter.drawScreenText(g2d,
|
||||
new Rectangle2D.Double(screenX, screenY, screenW, screenH), getScreenInk(), getScreen(),
|
||||
getName(), getValueForDisplay());
|
||||
drawCharacterMatrix(g2d, new Rectangle2D.Double(screenX, screenY, screenW, screenH));
|
||||
|
||||
// Only the parallel row has anywhere to print. The backpack's column sits 2.5 mm from the
|
||||
// left edge with the bezel 1.4 mm further in, so a label beside it would lie across the metal
|
||||
@@ -322,6 +323,71 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
g2d.setComposite(oldComposite);
|
||||
}
|
||||
|
||||
/**
|
||||
* The character grid, centred in the lit area: every cell's dots in the unlit shade first, then
|
||||
* the dots the text lights over the top of them. The matrix is narrower than the glass on both
|
||||
* modules, which is why it is centred rather than inset by a margin.
|
||||
*/
|
||||
private void drawCharacterMatrix(Graphics2D g2d, Rectangle2D screenArea) {
|
||||
double dotSize = DOT_SIZE.convertToPixels();
|
||||
double dotPitch = DOT_PITCH.convertToPixels();
|
||||
double charPitchX = CHAR_PITCH_X.convertToPixels();
|
||||
double charPitchY = CHAR_PITCH_Y.convertToPixels();
|
||||
|
||||
double cellWidth = (DOT_COLUMNS - 1) * dotPitch + dotSize;
|
||||
double cellHeight = (DOT_ROWS - 1) * dotPitch + dotSize;
|
||||
double matrixWidth = (lcdSize.getColumns() - 1) * charPitchX + cellWidth;
|
||||
double matrixHeight = (lcdSize.getRows() - 1) * charPitchY + cellHeight;
|
||||
double matrixX = screenArea.getX() + (screenArea.getWidth() - matrixWidth) / 2.0;
|
||||
double matrixY = screenArea.getY() + (screenArea.getHeight() - matrixHeight) / 2.0;
|
||||
|
||||
// The 20x4 comes to 3200 dots, so the rectangle is reused rather than allocated per dot, and
|
||||
// the two shades are laid down in one pass each rather than a colour change per dot.
|
||||
Rectangle2D.Double dot = new Rectangle2D.Double(0, 0, dotSize, dotSize);
|
||||
|
||||
g2d.setColor(getDotColor());
|
||||
for (int row = 0; row < lcdSize.getRows(); row++) {
|
||||
for (int column = 0; column < lcdSize.getColumns(); column++) {
|
||||
double cellX = matrixX + column * charPitchX;
|
||||
double cellY = matrixY + row * charPitchY;
|
||||
for (int dotRow = 0; dotRow < DOT_ROWS; dotRow++) {
|
||||
for (int dotColumn = 0; dotColumn < DOT_COLUMNS; dotColumn++) {
|
||||
dot.x = cellX + dotColumn * dotPitch;
|
||||
dot.y = cellY + dotRow * dotPitch;
|
||||
g2d.fill(dot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<String> lines = DotMatrixScreen.layOutText(getScreen(), getName(),
|
||||
getValueForDisplay(), lcdSize.getColumns(), lcdSize.getRows());
|
||||
if (lines.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
g2d.setColor(getScreenInk());
|
||||
int firstRow = (lcdSize.getRows() - lines.size()) / 2;
|
||||
for (int line = 0; line < lines.size(); line++) {
|
||||
String text = lines.get(line);
|
||||
int firstColumn = (lcdSize.getColumns() - text.length()) / 2;
|
||||
double cellY = matrixY + (firstRow + line) * charPitchY;
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
double cellX = matrixX + (firstColumn + i) * charPitchX;
|
||||
for (int dotRow = 0; dotRow < DotMatrixFont.GLYPH_HEIGHT; dotRow++) {
|
||||
for (int dotColumn = 0; dotColumn < DotMatrixFont.GLYPH_WIDTH; dotColumn++) {
|
||||
if (DotMatrixFont.isDotLit(c, dotRow, dotColumn)) {
|
||||
dot.x = cellX + dotColumn * dotPitch;
|
||||
dot.y = cellY + dotRow * dotPitch;
|
||||
g2d.fill(dot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||
g2d.setColor(PCB_GREEN);
|
||||
@@ -340,36 +406,47 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
public enum LCDSize {
|
||||
// Labels reach the BOM's value column, so they stay short; the dimensions are carried by the
|
||||
// fields beside them. The bezel and lit area are measured off the module rather than derived as
|
||||
// margins off the board, which made the window grow with the board.
|
||||
_16x2("16x2", 80.0, 35.0, 72.2, 24.1, 64.5, 14.5),
|
||||
_20x4("20x4", 98.0, 60.0, 96.8, 39.3, 77.0, 25.2);
|
||||
// margins off the board, which made the window grow with the board. The last figure is where
|
||||
// the parallel header starts, which the two modules do not agree on.
|
||||
_16x2("16x2", 16, 2, 80.0, 36.0, 72.2, 24.1, 64.5, 14.5, 7.5),
|
||||
_20x4("20x4", 20, 4, 98.0, 60.0, 96.8, 39.3, 77.0, 25.2, 10.0);
|
||||
|
||||
private final String label;
|
||||
private final int columns;
|
||||
private final int rows;
|
||||
private final double widthMm;
|
||||
private final double heightMm;
|
||||
private final double bezelWidthMm;
|
||||
private final double bezelHeightMm;
|
||||
private final double displayWidthMm;
|
||||
private final double displayHeightMm;
|
||||
private final double parallelHeaderInsetMm;
|
||||
|
||||
LCDSize(String label, double widthMm, double heightMm, double bezelWidthMm,
|
||||
double bezelHeightMm, double displayWidthMm, double displayHeightMm) {
|
||||
LCDSize(String label, int columns, int rows, double widthMm, double heightMm,
|
||||
double bezelWidthMm, double bezelHeightMm, double displayWidthMm, double displayHeightMm,
|
||||
double parallelHeaderInsetMm) {
|
||||
this.label = label;
|
||||
this.columns = columns;
|
||||
this.rows = rows;
|
||||
this.widthMm = widthMm;
|
||||
this.heightMm = heightMm;
|
||||
this.bezelWidthMm = bezelWidthMm;
|
||||
this.bezelHeightMm = bezelHeightMm;
|
||||
this.displayWidthMm = displayWidthMm;
|
||||
this.displayHeightMm = displayHeightMm;
|
||||
this.parallelHeaderInsetMm = parallelHeaderInsetMm;
|
||||
}
|
||||
|
||||
@Override public String toString() { return label; }
|
||||
public int getColumns() { return columns; }
|
||||
public int getRows() { return rows; }
|
||||
public double getWidthMm() { return widthMm; }
|
||||
public double getHeightMm() { return heightMm; }
|
||||
public double getBezelWidthMm() { return bezelWidthMm; }
|
||||
public double getBezelHeightMm() { return bezelHeightMm; }
|
||||
public double getDisplayWidthMm() { return displayWidthMm; }
|
||||
public double getDisplayHeightMm() { return displayHeightMm; }
|
||||
public double getParallelHeaderInsetMm() { return parallelHeaderInsetMm; }
|
||||
}
|
||||
|
||||
public enum LCDInterface {
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
/**
|
||||
* The glyphs an HD44780 character LCD paints in its 5x8 cell, one entry per printable ASCII
|
||||
* character. A glyph is stored as five columns, one per dot column, with bit 0 the top row -- the
|
||||
* order the controller itself clocks them out.
|
||||
*
|
||||
* <p>Most glyphs sit on a baseline in the seventh row and leave the eighth empty, which is where
|
||||
* the cursor would sit. The ones that descend below the baseline on a real module -- the lower-case
|
||||
* g, j, p, q and y, the comma, the semicolon and the underscore -- do use that eighth row, so they
|
||||
* are drawn to the full eight.
|
||||
*
|
||||
* <p>The table is spelled out rather than sampled from a system font because a rendered project has
|
||||
* to come out identical on every machine, and a font that may or may not be installed cannot
|
||||
* promise that.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class DotMatrixFont {
|
||||
|
||||
public static final int GLYPH_WIDTH = 5;
|
||||
public static final int GLYPH_HEIGHT = 8;
|
||||
|
||||
private static final char FIRST_CHAR = ' ';
|
||||
private static final char LAST_CHAR = '~';
|
||||
// what stands in for anything the table has no glyph for, as the controller's ROM does
|
||||
private static final char FALLBACK_CHAR = '?';
|
||||
|
||||
private static final int[] GLYPH_COLUMNS = new int[] {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // space
|
||||
0x00, 0x00, 0x5F, 0x00, 0x00, // !
|
||||
0x00, 0x07, 0x00, 0x07, 0x00, // "
|
||||
0x14, 0x7F, 0x14, 0x7F, 0x14, // #
|
||||
0x24, 0x2A, 0x7F, 0x2A, 0x12, // $
|
||||
0x23, 0x13, 0x08, 0x64, 0x62, // %
|
||||
0x36, 0x49, 0x55, 0x22, 0x50, // &
|
||||
0x00, 0x05, 0x03, 0x00, 0x00, // '
|
||||
0x00, 0x1C, 0x22, 0x41, 0x00, // (
|
||||
0x00, 0x41, 0x22, 0x1C, 0x00, // )
|
||||
0x14, 0x08, 0x3E, 0x08, 0x14, // *
|
||||
0x08, 0x08, 0x3E, 0x08, 0x08, // +
|
||||
0x00, 0x80, 0xC0, 0x40, 0x00, // ,
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, // -
|
||||
0x00, 0x60, 0x60, 0x00, 0x00, // .
|
||||
0x20, 0x10, 0x08, 0x04, 0x02, // /
|
||||
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
|
||||
0x00, 0x42, 0x7F, 0x40, 0x00, // 1
|
||||
0x42, 0x61, 0x51, 0x49, 0x46, // 2
|
||||
0x21, 0x41, 0x45, 0x4B, 0x31, // 3
|
||||
0x18, 0x14, 0x12, 0x7F, 0x10, // 4
|
||||
0x27, 0x45, 0x45, 0x45, 0x39, // 5
|
||||
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
|
||||
0x01, 0x71, 0x09, 0x05, 0x03, // 7
|
||||
0x36, 0x49, 0x49, 0x49, 0x36, // 8
|
||||
0x06, 0x49, 0x49, 0x29, 0x1E, // 9
|
||||
0x00, 0x36, 0x36, 0x00, 0x00, // :
|
||||
0x80, 0x76, 0x36, 0x00, 0x00, // ;
|
||||
0x00, 0x08, 0x14, 0x22, 0x41, // <
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, // =
|
||||
0x41, 0x22, 0x14, 0x08, 0x00, // >
|
||||
0x02, 0x01, 0x51, 0x09, 0x06, // ?
|
||||
0x32, 0x49, 0x79, 0x41, 0x3E, // @
|
||||
0x7E, 0x11, 0x11, 0x11, 0x7E, // A
|
||||
0x7F, 0x49, 0x49, 0x49, 0x36, // B
|
||||
0x3E, 0x41, 0x41, 0x41, 0x22, // C
|
||||
0x7F, 0x41, 0x41, 0x22, 0x1C, // D
|
||||
0x7F, 0x49, 0x49, 0x49, 0x41, // E
|
||||
0x7F, 0x09, 0x09, 0x09, 0x01, // F
|
||||
0x3E, 0x41, 0x49, 0x49, 0x7A, // G
|
||||
0x7F, 0x08, 0x08, 0x08, 0x7F, // H
|
||||
0x00, 0x41, 0x7F, 0x41, 0x00, // I
|
||||
0x20, 0x40, 0x41, 0x3F, 0x01, // J
|
||||
0x7F, 0x08, 0x14, 0x22, 0x41, // K
|
||||
0x7F, 0x40, 0x40, 0x40, 0x40, // L
|
||||
0x7F, 0x02, 0x0C, 0x02, 0x7F, // M
|
||||
0x7F, 0x04, 0x08, 0x10, 0x7F, // N
|
||||
0x3E, 0x41, 0x41, 0x41, 0x3E, // O
|
||||
0x7F, 0x09, 0x09, 0x09, 0x06, // P
|
||||
0x3E, 0x41, 0x51, 0x21, 0x5E, // Q
|
||||
0x7F, 0x09, 0x19, 0x29, 0x46, // R
|
||||
0x46, 0x49, 0x49, 0x49, 0x31, // S
|
||||
0x01, 0x01, 0x7F, 0x01, 0x01, // T
|
||||
0x3F, 0x40, 0x40, 0x40, 0x3F, // U
|
||||
0x1F, 0x20, 0x40, 0x20, 0x1F, // V
|
||||
0x3F, 0x40, 0x38, 0x40, 0x3F, // W
|
||||
0x63, 0x14, 0x08, 0x14, 0x63, // X
|
||||
0x07, 0x08, 0x70, 0x08, 0x07, // Y
|
||||
0x61, 0x51, 0x49, 0x45, 0x43, // Z
|
||||
0x00, 0x7F, 0x41, 0x41, 0x00, // [
|
||||
0x02, 0x04, 0x08, 0x10, 0x20, // \
|
||||
0x00, 0x41, 0x41, 0x7F, 0x00, // ]
|
||||
0x04, 0x02, 0x01, 0x02, 0x04, // ^
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, // _
|
||||
0x00, 0x01, 0x02, 0x04, 0x00, // `
|
||||
0x20, 0x54, 0x54, 0x54, 0x78, // a
|
||||
0x7F, 0x48, 0x44, 0x44, 0x38, // b
|
||||
0x38, 0x44, 0x44, 0x44, 0x20, // c
|
||||
0x38, 0x44, 0x44, 0x48, 0x7F, // d
|
||||
0x38, 0x54, 0x54, 0x54, 0x18, // e
|
||||
0x08, 0x7E, 0x09, 0x01, 0x02, // f
|
||||
0x18, 0xA4, 0xA4, 0xA4, 0x7C, // g
|
||||
0x7F, 0x08, 0x04, 0x04, 0x78, // h
|
||||
0x00, 0x44, 0x7D, 0x40, 0x00, // i
|
||||
0x40, 0x80, 0x80, 0x7D, 0x00, // j
|
||||
0x7F, 0x10, 0x28, 0x44, 0x00, // k
|
||||
0x00, 0x41, 0x7F, 0x40, 0x00, // l
|
||||
0x7C, 0x04, 0x18, 0x04, 0x78, // m
|
||||
0x7C, 0x08, 0x04, 0x04, 0x78, // n
|
||||
0x38, 0x44, 0x44, 0x44, 0x38, // o
|
||||
0xFC, 0x24, 0x24, 0x24, 0x18, // p
|
||||
0x18, 0x24, 0x24, 0x24, 0xFC, // q
|
||||
0x7C, 0x08, 0x04, 0x04, 0x08, // r
|
||||
0x48, 0x54, 0x54, 0x54, 0x20, // s
|
||||
0x04, 0x3F, 0x44, 0x40, 0x20, // t
|
||||
0x3C, 0x40, 0x40, 0x20, 0x7C, // u
|
||||
0x1C, 0x20, 0x40, 0x20, 0x1C, // v
|
||||
0x3C, 0x40, 0x30, 0x40, 0x3C, // w
|
||||
0x44, 0x28, 0x10, 0x28, 0x44, // x
|
||||
0x1C, 0xA0, 0xA0, 0xA0, 0x7C, // y
|
||||
0x44, 0x64, 0x54, 0x4C, 0x44, // z
|
||||
0x00, 0x08, 0x36, 0x41, 0x00, // {
|
||||
0x00, 0x00, 0x7F, 0x00, 0x00, // |
|
||||
0x00, 0x41, 0x36, 0x08, 0x00, // }
|
||||
0x08, 0x08, 0x2A, 0x1C, 0x08, // ~
|
||||
};
|
||||
|
||||
private DotMatrixFont() {}
|
||||
|
||||
/** Whether the glyph for a character lights the dot at the given row and column of its cell. */
|
||||
public static boolean isDotLit(char c, int row, int column) {
|
||||
if (row < 0 || row >= GLYPH_HEIGHT || column < 0 || column >= GLYPH_WIDTH) {
|
||||
return false;
|
||||
}
|
||||
char glyph = c < FIRST_CHAR || c > LAST_CHAR ? FALLBACK_CHAR : c;
|
||||
return (GLYPH_COLUMNS[(glyph - FIRST_CHAR) * GLYPH_WIDTH + column] >> row & 1) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.diylc.common.Display;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
|
||||
/**
|
||||
* Text as a dot-matrix screen actually shows it: folded into the character grid the panel has room
|
||||
* for, then painted one lit pixel at a time out of {@link DotMatrixFont}.
|
||||
*
|
||||
* <p>Laying the text out is shared by every such screen; painting it is not. A graphic panel like
|
||||
* the Nokia's addresses one unbroken grid of pixels, and {@link #drawText} serves those. A
|
||||
* character module instead has gaps between its cells, so it walks its own cells and only borrows
|
||||
* the layout.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class DotMatrixScreen {
|
||||
|
||||
/** A glyph and the blank column that separates it from the next, as the panel fonts are drawn. */
|
||||
public static final int CELL_WIDTH = DotMatrixFont.GLYPH_WIDTH + 1;
|
||||
/** Rows butt against each other; the glyph's own last row is the gap. */
|
||||
public static final int CELL_HEIGHT = DotMatrixFont.GLYPH_HEIGHT;
|
||||
|
||||
private DotMatrixScreen() {}
|
||||
|
||||
/**
|
||||
* What the screen spells out, broken into grid rows. A line too wide for the grid is folded at a
|
||||
* space where there is one and cut where there is not, and whatever runs past the last row is
|
||||
* dropped: a real panel shows what fits and nothing more.
|
||||
*/
|
||||
public static List<String> layOutText(Display display, String name, String value, int columns,
|
||||
int rows) {
|
||||
String text = MakerBoardPainter.screenText(display, name, value);
|
||||
if (text == null || columns <= 0 || rows <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> lines = new ArrayList<String>();
|
||||
for (String paragraph : text.split("\n")) {
|
||||
String remaining = paragraph.trim();
|
||||
while (!remaining.isEmpty() && lines.size() < rows) {
|
||||
if (remaining.length() <= columns) {
|
||||
lines.add(remaining);
|
||||
break;
|
||||
}
|
||||
int fold = remaining.lastIndexOf(' ', columns);
|
||||
if (fold <= 0) {
|
||||
fold = columns;
|
||||
}
|
||||
lines.add(remaining.substring(0, fold).trim());
|
||||
remaining = remaining.substring(fold).trim();
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints laid-out text onto an unbroken pixel grid, centred in it. The caller has already set the
|
||||
* ink colour and placed the grid; this only decides which pixels light.
|
||||
*
|
||||
* <p>Centring is worked out in pixels rather than whole cells. A panel addresses single pixels,
|
||||
* and a line that nearly fills the grid has no whole cell to spare, so rounding to one would pin
|
||||
* it against the left edge and leave the slack -- the trailing cell's blank column, plus whatever
|
||||
* the cell width does not divide into the panel -- piled up on the right.
|
||||
*
|
||||
* @param lines text from {@link #layOutText}
|
||||
* @param pixelsX pixels the panel is wide
|
||||
* @param pixelsY pixels the panel is tall
|
||||
* @param x left edge of the pixel grid
|
||||
* @param y top edge of the pixel grid
|
||||
* @param pixelWidth width of one display pixel
|
||||
* @param pixelHeight height of one display pixel, which need not match its width
|
||||
*/
|
||||
public static void drawText(Graphics2D g2d, List<String> lines, int pixelsX, int pixelsY,
|
||||
double x, double y, double pixelWidth, double pixelHeight) {
|
||||
if (lines.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// a panel runs to thousands of pixels, so the rectangle is reused rather than allocated per dot
|
||||
Rectangle2D.Double pixel = new Rectangle2D.Double(0, 0, pixelWidth, pixelHeight);
|
||||
|
||||
// the offsets land on whole pixels because that is all the controller can address
|
||||
double firstRow = Math.floor((pixelsY - lines.size() * CELL_HEIGHT) / 2.0);
|
||||
for (int line = 0; line < lines.size(); line++) {
|
||||
String text = lines.get(line);
|
||||
// the spacer column trailing the last glyph is not part of the text, so it is not centred
|
||||
// with it
|
||||
int lineWidth = text.length() * CELL_WIDTH - (CELL_WIDTH - DotMatrixFont.GLYPH_WIDTH);
|
||||
double firstColumn = Math.floor((pixelsX - lineWidth) / 2.0);
|
||||
double cellY = y + (firstRow + line * CELL_HEIGHT) * pixelHeight;
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
double cellX = x + (firstColumn + i * CELL_WIDTH) * pixelWidth;
|
||||
for (int dotRow = 0; dotRow < DotMatrixFont.GLYPH_HEIGHT; dotRow++) {
|
||||
for (int dotColumn = 0; dotColumn < DotMatrixFont.GLYPH_WIDTH; dotColumn++) {
|
||||
if (DotMatrixFont.isDotLit(c, dotRow, dotColumn)) {
|
||||
pixel.x = cellX + dotColumn * pixelWidth;
|
||||
pixel.y = cellY + dotRow * pixelHeight;
|
||||
g2d.fill(pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,11 @@ public class Nokia5110LCD extends AbstractMakerBoard {
|
||||
public static Size CORNER_RADIUS = new Size(1.5d, SizeUnit.mm);
|
||||
public static Size SILK_TEXT_GAP = new Size(1.0d, SizeUnit.mm);
|
||||
|
||||
// The PCD8544's own addressable grid. Its pixels are not square, the glass being wider than it
|
||||
// is tall by more than 84:48, so the two pitches are worked out separately.
|
||||
public static final int PIXELS_X = 84;
|
||||
public static final int PIXELS_Y = 48;
|
||||
|
||||
public static final String[] PIN_NAMES =
|
||||
new String[] {"RST", "CE", "DC", "DIN", "CLK", "VCC", "BL", "GND"};
|
||||
|
||||
@@ -253,9 +258,14 @@ public class Nokia5110LCD extends AbstractMakerBoard {
|
||||
g2d.draw(new RoundRectangle2D.Double(displayX, displayY, displayW, displayH, displayRadius,
|
||||
displayRadius));
|
||||
|
||||
MakerBoardPainter.drawScreenText(g2d,
|
||||
new Rectangle2D.Double(displayX, displayY, displayW, displayH), getScreenInk(),
|
||||
getScreen(), getName(), getValueForDisplay());
|
||||
// The panel has no other way to show text, so the label is drawn the way the module would
|
||||
// put it there: lit pixels of its own grid rather than a smooth font laid over the glass.
|
||||
int columns = PIXELS_X / DotMatrixScreen.CELL_WIDTH;
|
||||
int rows = PIXELS_Y / DotMatrixScreen.CELL_HEIGHT;
|
||||
g2d.setColor(getScreenInk());
|
||||
DotMatrixScreen.drawText(g2d,
|
||||
DotMatrixScreen.layOutText(getScreen(), getName(), getValueForDisplay(), columns, rows),
|
||||
PIXELS_X, PIXELS_Y, displayX, displayY, displayW / PIXELS_X, displayH / PIXELS_Y);
|
||||
|
||||
// Names lie flat along the row: the strip is 3.5 mm deep and a label stood on end needs
|
||||
// 4.7 mm.
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
import org.diylc.awt.StringUtils;
|
||||
@@ -346,9 +345,16 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
g2d.setColor(ACTIVE_AREA_COLOR);
|
||||
g2d.fill(new RoundRectangle2D.Double(activeX, activeY, activeW, activeH, 2, 2));
|
||||
|
||||
MakerBoardPainter.drawScreenText(g2d,
|
||||
new Rectangle2D.Double(activeX, activeY, activeW, activeH), getPixelColor(), getScreen(),
|
||||
getName(), getValueForDisplay());
|
||||
// Drawn as lit pixels of the controller's own grid, which is the only way this panel can
|
||||
// put text on the glass.
|
||||
int pixelsX = version.getPixelsX();
|
||||
int pixelsY = version.getPixelsY();
|
||||
int columns = pixelsX / DotMatrixScreen.CELL_WIDTH;
|
||||
int rows = pixelsY / DotMatrixScreen.CELL_HEIGHT;
|
||||
g2d.setColor(getPixelColor());
|
||||
DotMatrixScreen.drawText(g2d,
|
||||
DotMatrixScreen.layOutText(getScreen(), getName(), getValueForDisplay(), columns, rows),
|
||||
pixelsX, pixelsY, activeX, activeY, activeW / pixelsX, activeH / pixelsY);
|
||||
|
||||
drawPinNames(g2d, boardX, boardY);
|
||||
}
|
||||
@@ -405,15 +411,17 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
// Panel and lit-area sizes belong to the display part and are shared by both of a size's
|
||||
// boards. Everything belonging to a PCB is on the Layout, including where the lit area sits
|
||||
// down the board, which the two 0.91" boards disagree on.
|
||||
SSD1306_0_91("0.91\" SSD1306 128x32", 30.0d, 12.0d, 22.384d, 5.584d, 2.1d,
|
||||
SSD1306_0_91("0.91\" SSD1306 128x32", 128, 32, 30.0d, 12.0d, 22.384d, 5.584d, 2.1d,
|
||||
new Layout(35.8d, 12.0d, 1.5d, true, 0d, 0d, 5.0d, 0d, 2.35d),
|
||||
new Layout(32.5d, 20.5d, 1.5d, false, 2.5d, 2.0d, 1.25d, 4.0d, 6.6d)),
|
||||
SSD1306_0_96("0.96\" SSD1306 128x64", 26.7d, 19.26d, 21.74d, 11.0d, 2.48d,
|
||||
SSD1306_0_96("0.96\" SSD1306 128x64", 128, 64, 26.7d, 19.26d, 21.74d, 11.0d, 2.48d,
|
||||
new Layout(27.0d, 27.0d, 1.6d, false, 2.0d, 2.0d, 0.15d, 3.87d, 6.14d)),
|
||||
SH1106_1_3("1.3\" SH1106 128x64", 35.4d, 23.0d, 29.42d, 14.7d, 2.99d,
|
||||
SH1106_1_3("1.3\" SH1106 128x64", 128, 64, 35.4d, 23.0d, 29.42d, 14.7d, 2.99d,
|
||||
new Layout(35.4d, 33.5d, 1.5d, false, 2.5d, 3.0d, 0d, 5.25d, 7.35d));
|
||||
|
||||
private final String label;
|
||||
private final int pixelsX;
|
||||
private final int pixelsY;
|
||||
private final double panelWidthMm;
|
||||
private final double panelLengthMm;
|
||||
private final double activeWidthMm;
|
||||
@@ -423,15 +431,18 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
private final Layout spiLayout;
|
||||
|
||||
/** For a size sold on one board, which both headers are built onto. */
|
||||
Version(String label, double panelWidthMm, double panelLengthMm, double activeWidthMm,
|
||||
double activeLengthMm, double activeLeftInPanelMm, Layout layout) {
|
||||
this(label, panelWidthMm, panelLengthMm, activeWidthMm, activeLengthMm, activeLeftInPanelMm,
|
||||
layout, layout);
|
||||
Version(String label, int pixelsX, int pixelsY, double panelWidthMm, double panelLengthMm,
|
||||
double activeWidthMm, double activeLengthMm, double activeLeftInPanelMm, Layout layout) {
|
||||
this(label, pixelsX, pixelsY, panelWidthMm, panelLengthMm, activeWidthMm, activeLengthMm,
|
||||
activeLeftInPanelMm, layout, layout);
|
||||
}
|
||||
|
||||
Version(String label, double panelWidthMm, double panelLengthMm, double activeWidthMm,
|
||||
double activeLengthMm, double activeLeftInPanelMm, Layout i2cLayout, Layout spiLayout) {
|
||||
Version(String label, int pixelsX, int pixelsY, double panelWidthMm, double panelLengthMm,
|
||||
double activeWidthMm, double activeLengthMm, double activeLeftInPanelMm, Layout i2cLayout,
|
||||
Layout spiLayout) {
|
||||
this.label = label;
|
||||
this.pixelsX = pixelsX;
|
||||
this.pixelsY = pixelsY;
|
||||
this.panelWidthMm = panelWidthMm;
|
||||
this.panelLengthMm = panelLengthMm;
|
||||
this.activeWidthMm = activeWidthMm;
|
||||
@@ -444,6 +455,8 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
@Override public String toString() { return label; }
|
||||
public double getPanelWidthMm() { return panelWidthMm; }
|
||||
public double getPanelLengthMm() { return panelLengthMm; }
|
||||
public int getPixelsX() { return pixelsX; }
|
||||
public int getPixelsY() { return pixelsY; }
|
||||
public double getActiveWidthMm() { return activeWidthMm; }
|
||||
public double getActiveLengthMm() { return activeLengthMm; }
|
||||
public double getActiveLeftInPanelMm() { return activeLeftInPanelMm; }
|
||||
|
||||
@@ -91,6 +91,29 @@ public class CharacterLCDTest {
|
||||
MakerBoardTestSupport.assertScreenTextIsDrawn(pale, paleBlank);
|
||||
}
|
||||
|
||||
/**
|
||||
* The dot figures and the lit-area figures are measured off the module independently, so nothing
|
||||
* but this keeps them consistent: a character matrix wider or taller than the glass it sits on
|
||||
* means one of the two sets has drifted.
|
||||
*/
|
||||
@Test
|
||||
public void theCharacterMatrixFitsInsideTheLitArea() {
|
||||
double cellWidth = (CharacterLCD.DOT_COLUMNS - 1) * 0.6d + 0.55d;
|
||||
double cellHeight = (CharacterLCD.DOT_ROWS - 1) * 0.6d + 0.55d;
|
||||
|
||||
for (LCDSize lcdSize : LCDSize.values()) {
|
||||
double matrixWidth = (lcdSize.getColumns() - 1) * 3.55d + cellWidth;
|
||||
double matrixHeight = (lcdSize.getRows() - 1) * 5.35d + cellHeight;
|
||||
|
||||
Assert.assertTrue(lcdSize + " matrix is " + matrixWidth + " mm wide on a "
|
||||
+ lcdSize.getDisplayWidthMm() + " mm window",
|
||||
matrixWidth <= lcdSize.getDisplayWidthMm());
|
||||
Assert.assertTrue(lcdSize + " matrix is " + matrixHeight + " mm tall on a "
|
||||
+ lcdSize.getDisplayHeightMm() + " mm window",
|
||||
matrixHeight <= lcdSize.getDisplayHeightMm());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drawsCleanlyInEveryState() {
|
||||
for (LCDSize lcdSize : LCDSize.values()) {
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* The glyph table is 475 hand-entered figures, and a drawing that renders a character wrong looks
|
||||
* like a rendering bug rather than a bad byte, so the table is checked here instead.
|
||||
*/
|
||||
public class DotMatrixFontTest {
|
||||
|
||||
// where a glyph without a descender sits; the row below it is the descender and cursor row
|
||||
private static final int BASELINE_ROW = DotMatrixFont.GLYPH_HEIGHT - 2;
|
||||
|
||||
private static boolean[][] glyphOf(char c) {
|
||||
boolean[][] dots = new boolean[DotMatrixFont.GLYPH_HEIGHT][DotMatrixFont.GLYPH_WIDTH];
|
||||
for (int row = 0; row < DotMatrixFont.GLYPH_HEIGHT; row++) {
|
||||
for (int column = 0; column < DotMatrixFont.GLYPH_WIDTH; column++) {
|
||||
dots[row][column] = DotMatrixFont.isDotLit(c, row, column);
|
||||
}
|
||||
}
|
||||
return dots;
|
||||
}
|
||||
|
||||
private static boolean isBlank(char c) {
|
||||
for (boolean[] row : glyphOf(c)) {
|
||||
for (boolean lit : row) {
|
||||
if (lit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** A hole in the table would read as a blank cell on the glass, which is what this catches. */
|
||||
@Test
|
||||
public void everyPrintableCharacterHasAGlyph() {
|
||||
for (char c = '!'; c <= '~'; c++) {
|
||||
Assert.assertFalse("'" + c + "' has no glyph", isBlank(c));
|
||||
}
|
||||
Assert.assertTrue("a space must light nothing", isBlank(' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Spot-check against a glyph whose shape is unambiguous: a capital H is two uprights from the top
|
||||
* row down to the baseline, joined by a bar across the middle. A table shifted by one entry fails
|
||||
* here.
|
||||
*/
|
||||
@Test
|
||||
public void theGlyphsAreTheRightWayUpAndRound() {
|
||||
boolean[][] h = glyphOf('H');
|
||||
for (int row = 0; row <= BASELINE_ROW; row++) {
|
||||
Assert.assertTrue("left upright, row " + row, h[row][0]);
|
||||
Assert.assertTrue("right upright, row " + row, h[row][4]);
|
||||
// the bar joins them across the middle row only
|
||||
Assert.assertEquals("middle column, row " + row, row == 3, h[row][2]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The eighth row belongs to the descenders and to the cursor. A capital letter or a digit that
|
||||
* reached into it would collide with the cursor on a real module, and one of the five lower-case
|
||||
* descenders that did not reach it would be the squashed seven-row font this replaced.
|
||||
*/
|
||||
@Test
|
||||
public void onlyTheDescendersReachTheBottomRow() {
|
||||
String descenders = "gjpqy,;_";
|
||||
for (char c = ' '; c <= '~'; c++) {
|
||||
boolean reaches = false;
|
||||
for (int column = 0; column < DotMatrixFont.GLYPH_WIDTH; column++) {
|
||||
reaches |= DotMatrixFont.isDotLit(c, BASELINE_ROW + 1, column);
|
||||
}
|
||||
Assert.assertEquals("'" + c + "' in the bottom row", descenders.indexOf(c) >= 0, reaches);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nothingIsLitOutsideTheGlyph() {
|
||||
for (char c : new char[] {' ', 'A', 'g', '0', '~'}) {
|
||||
Assert.assertFalse("row past the cell",
|
||||
DotMatrixFont.isDotLit(c, DotMatrixFont.GLYPH_HEIGHT, 0));
|
||||
Assert.assertFalse("row above", DotMatrixFont.isDotLit(c, -1, 0));
|
||||
Assert.assertFalse("column past the glyph",
|
||||
DotMatrixFont.isDotLit(c, 0, DotMatrixFont.GLYPH_WIDTH));
|
||||
Assert.assertFalse("column before the glyph", DotMatrixFont.isDotLit(c, 0, -1));
|
||||
}
|
||||
}
|
||||
|
||||
/** Anything the table has no glyph for still has to print something. */
|
||||
@Test
|
||||
public void charactersOutsideTheTableFallBack() {
|
||||
for (char c : new char[] {'\u00e9', '\u0416', (char) 0x1F, (char) 0x7F}) {
|
||||
Assert.assertArrayEquals("'" + c + "' should print as a question mark", glyphOf('?'),
|
||||
glyphOf(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
|
||||
import org.diylc.common.Display;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Folding the label into the character grid, which the character LCD and both graphic panels now
|
||||
* share. A panel shows what fits and nothing else, so the rules about what gets dropped matter.
|
||||
*/
|
||||
public class DotMatrixScreenTest {
|
||||
|
||||
private static List<String> lines(String name, String value, int columns, int rows) {
|
||||
return DotMatrixScreen.layOutText(Display.BOTH, name, value, columns, rows);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textThatFitsIsLeftAlone() {
|
||||
Assert.assertEquals(List.of("LCD1", "16x2", "Parallel HD44780"),
|
||||
lines("LCD1", "16x2, Parallel HD44780", 16, 4));
|
||||
}
|
||||
|
||||
/** The value arrives as one string per property, and each starts its own row. */
|
||||
@Test
|
||||
public void everyPropertyStartsItsOwnRow() {
|
||||
Assert.assertEquals(List.of("0.96\" SSD1306", "128x64", "I2C"),
|
||||
DotMatrixScreen.layOutText(Display.VALUE, "OLED1", "0.96\" SSD1306, 128x64, I2C", 21, 8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aLongLineFoldsAtASpace() {
|
||||
Assert.assertEquals(List.of("LCD1", "Parallel", "HD44780"),
|
||||
lines("LCD1", "Parallel HD44780", 8, 4));
|
||||
}
|
||||
|
||||
/** A word with no space in it has to be cut, or it would run off the glass. */
|
||||
@Test
|
||||
public void aWordTooWideIsCut() {
|
||||
Assert.assertEquals(List.of("ABCDE", "FGHIJ", "KL"),
|
||||
DotMatrixScreen.layOutText(Display.NAME, "ABCDEFGHIJKL", null, 5, 4));
|
||||
}
|
||||
|
||||
/** What runs past the last row is dropped rather than shrunk or scrolled. */
|
||||
@Test
|
||||
public void whatRunsPastTheLastRowIsDropped() {
|
||||
Assert.assertEquals(List.of("LCD1", "16x2"), lines("LCD1", "16x2, Parallel HD44780", 16, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders onto a one-pixel-per-pixel grid and reports the margins the text leaves either side,
|
||||
* which is the only way to see where it actually landed.
|
||||
*/
|
||||
private static int[] margins(List<String> lines, int pixelsX, int pixelsY) {
|
||||
BufferedImage image = new BufferedImage(pixelsX, pixelsY, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
g2d.setColor(Color.WHITE);
|
||||
DotMatrixScreen.drawText(g2d, lines, pixelsX, pixelsY, 0, 0, 1, 1);
|
||||
g2d.dispose();
|
||||
|
||||
int first = -1;
|
||||
int last = -1;
|
||||
for (int x = 0; x < pixelsX; x++) {
|
||||
for (int y = 0; y < pixelsY; y++) {
|
||||
if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
|
||||
first = first == -1 ? x : first;
|
||||
last = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.assertNotEquals("nothing was drawn", -1, first);
|
||||
return new int[] {first, pixelsX - 1 - last};
|
||||
}
|
||||
|
||||
/**
|
||||
* A line that nearly fills the panel used to be pinned against the left edge: centring rounded to
|
||||
* whole cells, and a cell is a glyph plus a blank column, so the slack all ended up on the right.
|
||||
* The 0.96" OLED's own label is exactly that case, 20 characters on a 21-column panel.
|
||||
*/
|
||||
@Test
|
||||
public void aNearlyFullLineIsStillCentred() {
|
||||
int[] m = margins(List.of("0.96\" SSD1306 128x64"), 128, 64);
|
||||
Assert.assertTrue("left " + m[0] + " vs right " + m[1], Math.abs(m[0] - m[1]) <= 1);
|
||||
Assert.assertTrue("should not touch the edge, left margin was " + m[0], m[0] > 0);
|
||||
}
|
||||
|
||||
/** The Nokia's label is 13 characters on a 14-column panel, the same case one column narrower. */
|
||||
@Test
|
||||
public void theNokiaLabelIsCentred() {
|
||||
int[] m = margins(List.of("84x48 PCD8544"), 84, 48);
|
||||
Assert.assertTrue("left " + m[0] + " vs right " + m[1], Math.abs(m[0] - m[1]) <= 1);
|
||||
Assert.assertTrue("should not touch the edge, left margin was " + m[0], m[0] > 0);
|
||||
}
|
||||
|
||||
/** A short line has whole cells to spare, and must come out centred just the same. */
|
||||
@Test
|
||||
public void aShortLineIsCentred() {
|
||||
int[] m = margins(List.of("I2C"), 128, 64);
|
||||
Assert.assertTrue("left " + m[0] + " vs right " + m[1], Math.abs(m[0] - m[1]) <= 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aScreenTurnedOffPrintsNothing() {
|
||||
Assert.assertTrue(
|
||||
DotMatrixScreen.layOutText(Display.NONE, "LCD1", "16x2", 16, 2).isEmpty());
|
||||
}
|
||||
|
||||
/** A grid with no room at all must not throw its way out of a repaint. */
|
||||
@Test
|
||||
public void anEmptyGridPrintsNothing() {
|
||||
Assert.assertTrue(lines("LCD1", "16x2", 0, 2).isEmpty());
|
||||
Assert.assertTrue(lines("LCD1", "16x2", 16, 0).isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>6.6.0</version>
|
||||
<version>6.7.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-swing</artifactId>
|
||||
@@ -116,7 +116,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.22.1</version>
|
||||
<version>2.22.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
|
||||
@@ -0,0 +1,619 @@
|
||||
# Audio Coverage Roadmap
|
||||
|
||||
Closing the gap between the component library and what the guitar, amplifier, pedal and hi-fi
|
||||
communities actually build.
|
||||
|
||||
Status: proposed
|
||||
Scope: `diylc-library` (`components.passive`, `.tube`, `.electromechanical`, `.guitar`,
|
||||
`.connectivity`), one bug fix in `diylc-core/serialization`
|
||||
Companion: the sibling roadmaps in this directory, which apply the same method to other packages
|
||||
|
||||
## 1. Goal
|
||||
|
||||
The audio side of the library has never had a systematic coverage pass, and it is the side the
|
||||
sample corpus is made of. The headline number: **three components in the `Tubes` category**,
|
||||
against 50 corpus projects that place a tube socket.
|
||||
|
||||
This document inventories what ships, measures what people hand-draw because it does not exist,
|
||||
and sorts the additions into three tiers by evidence of demand rather than by appetite.
|
||||
|
||||
## 2. Decisions that govern this roadmap
|
||||
|
||||
| # | Decision | Choice | Rationale |
|
||||
|---|---|---|---|
|
||||
| D1 | What counts as a gap | **A part people are drawing by hand today.** Measured from label text in `diylc-regression-data/input`, not from catalogue browsing. | Keeps the roadmap honest. Several things that looked like gaps from the palette listing (pedal enclosures, EMG pickups, Filtertrons, Tele single coils, the switch-type matrix) turned out to ship already; several things that look exotic turn out to be in 25 projects. |
|
||||
| D2 | Evidence vs. judgement | **Tiers 1 and 2 are measured. Tier 3 is reasoned and labelled as such.** | The corpus is overwhelmingly pedal and amp. It cannot speak to hi-fi demand at all, and saying so is better than inventing a number. See §4.3. |
|
||||
| D3 | New class vs. new enum constant | **Same footprint family → enum constant; different structure → new class.** | Already the established paradigm, and `diylc/CLAUDE.md` states the preference directly: a `Version` property on one class beats a class per revision. |
|
||||
| D4 | Where iron lives | **One `Transformer` class with a `Type` property (power / output / choke), in `components.passive`.** Not three classes. | A bell-end power transformer, an output transformer and a choke share an outline, a mounting footprint and a tap-strip; they differ in tap count and label. `AudioTransformer` already sits in `passive` and already extends `AbstractMultiPartComponent`, so the neighbour is right there. |
|
||||
| D5 | Backward compatibility | Append-only enums, unchanged defaults, unchanged control-point counts for existing variants. | `.diy` files are a public contract. Only §6.1 touches serialization, and it *restores* compatibility rather than risking it. |
|
||||
| D6 | Pre-set data before components | **Where a variant or a datasheet row closes a gap, it ships before any new class.** | Both are configuration rather than Java, and both are already covered by tests. §4.2 shows which of the most-placed parts have neither. |
|
||||
| D7 | Composable parts are not components | **If a part can be assembled from existing components and saved as a building block, it ships as a factory building block, not a new class.** | A new class has to be drawn, transformed, tested, BOM-policed and supported forever, and it crowds the toolbox. A building block is data. The test is whether the assembly is electrically and visually honest without a new primitive: a push-pull pot is a pot plus a DPDT switch and passes, a chassis transformer is not a rectangle plus lugs and fails. See §5.4. |
|
||||
|
||||
### Explicitly out of scope
|
||||
|
||||
- The dormant classes outside the packages in scope above. They were parked deliberately
|
||||
("comment out future stuff", `27eb2599`) and belong to the roadmaps for their own packages.
|
||||
- Any electrical simulation: load-line *plotting* is in scope only because the component already
|
||||
exists (§6.2), impedance and frequency-response maths are not.
|
||||
- Speaker cabinet and enclosure mechanical drawing beyond what `ChassisPanel` already does.
|
||||
- Bumping the project version, packaging, and the deployment build.
|
||||
|
||||
## 3. Current coverage
|
||||
|
||||
**169 components register at runtime.** 208 classes carry a `@ComponentDescriptor`, but 39 have it
|
||||
commented out and are invisible to the annotation scan.
|
||||
|
||||
| Category | Active | | Category | Active |
|
||||
|---|---|---|---|---|
|
||||
| Schematic Symbols | 30 | | Semiconductors | 12 |
|
||||
| Electro-Mechanical | 28 | | Guitar | 12 |
|
||||
| Passive | 17 | | Boards | 12 |
|
||||
| Connectivity | 16 | | Misc | 10 |
|
||||
| Displays & Outputs | 13 | | Controllers | 10 |
|
||||
| Shapes | 4 | | **Tubes** | **3** |
|
||||
| SMD | 2 | | | |
|
||||
|
||||
What is genuinely strong and should not be touched: the switch matrix (`ToggleSwitchType` runs SPST
|
||||
through 5PDT with both on/on/on variants; sealed and open rotaries; Lever, LP, Freeway 3x3,
|
||||
Schaller Megaswitch in six types, S1), the pickup models (`SingleCoilPickup` does Strat and Tele
|
||||
bodies with rod/rail/dual-rail poles, `HumbuckerPickup` does PAF/Mini/Filtertron with an EMG-style
|
||||
variant), and resistor coverage (22 variants from 1/4 W carbon comp to 100 W ceramic).
|
||||
|
||||
### 3.1 Dormant classes relevant to audio
|
||||
|
||||
Five of the 39 fall inside this roadmap's scope. Two are finished code behind a commented
|
||||
annotation; three are not.
|
||||
|
||||
| Class | State | To revive |
|
||||
|---|---|---|
|
||||
| `passive/AirCoreInductor` | Body live, 4 commented descriptor lines | Uncomment + restore 4 imports |
|
||||
| `passive/BoxTrimmer` | Body live, 9 commented lines, `BoxTrimmerTransformer` exists | Uncomment + restore imports |
|
||||
| `misc/Loadline` | Body live, 4 commented lines, extends `AbstractBoard` | Uncomment + restore imports |
|
||||
| `passive/AxialAirCoreInductor` | **Stub** — 69 of 70 lines commented, and it extended `AbstractFilmCapacitor` | Rewrite, or delete the file |
|
||||
| `smd/SOT23Package` | **Stub** — 427 of 449 lines commented | Out of scope here |
|
||||
|
||||
## 4. Evidence
|
||||
|
||||
### 4.1 Method
|
||||
|
||||
`diylc-regression-data/input` holds 709 `.diy` projects (cloud 379, v1 284, user-files 34,
|
||||
diy-fever 12). The 284 v1-format files carry no class names, so component tallies reflect the 425
|
||||
modern files; label-text searches cover all 709.
|
||||
|
||||
**Searching raw file text does not work.** `Image` components store base64, and three-letter
|
||||
queries hit it: `XLR` returned 17 "files" of which zero were real. Every figure below comes from
|
||||
extracting `<text>`, `<title>`, `<name>`, `<value>` and `<description>` element contents first
|
||||
(118,619 strings) and searching those.
|
||||
|
||||
### 4.2 Two coverage mechanisms, and which parts have neither
|
||||
|
||||
Pre-set data reaches a component two ways, and they are easy to confuse.
|
||||
|
||||
**Variants** (`variants.xml`, 176 of them) are saved property sets, authored per class, listed under
|
||||
*Apply Variant*.
|
||||
|
||||
**Datasheets** are parametrized part catalogues: a `<ClassName>.datasheet` CSV on the classpath, one
|
||||
row per real orderable part, listed under *Apply Model*. Five capacitor classes have one, and
|
||||
between them they carry **3,539 rows covering 34 models** — each row a part number, a voltage, a
|
||||
capacitance, exact body dimensions in mm, and body/border/label/marker colours:
|
||||
|
||||
| Class | Rows | Models |
|
||||
|---|---|---|
|
||||
| `RadialFilmCapacitor` | 1,523 | 14 (WIMA MKS-4/MKP-4/FKP/FKS, Orange Drop 225P/715P/716P, Epcos MKT) |
|
||||
| `RadialElectrolytic` | 833 | 7 (Panasonic FC/FM/NHG, Nichicon FW/FG/KZ, Elna Silmic II) |
|
||||
| `AxialElectrolyticCapacitor` | 455 | 4 (F&T Typ A, Sprague TVA Atom, Illinois TTA, JJ ANH) |
|
||||
| `RadialMicaCapacitor` | 433 | 7 (Cornell Dubilier CD10 through CD42, CDV19/CDV30) |
|
||||
| `AxialFilmCapacitor` | 293 | 2 (Solen Fast, Mallory 150) |
|
||||
|
||||
**Capacitors are therefore already covered, at a fidelity no variant could reach** — exact
|
||||
dimensions per value and voltage, not a size estimate. Any roadmap item proposing to "add
|
||||
Mallory 150 / Orange Drop / WIMA variants" is proposing something that shipped long ago; an
|
||||
earlier draft of this document made exactly that mistake by counting only `variants.xml`.
|
||||
|
||||
The gap is the classes with *neither* mechanism:
|
||||
|
||||
| Component | Placements | Variants | Datasheet |
|
||||
|---|---|---|---|
|
||||
| `Resistor` | 2,388 | 22 | — |
|
||||
| `RadialFilmCapacitor` | 754 | 0 | 1,523 rows |
|
||||
| `RadialElectrolytic` | 602 | 0 | 833 rows |
|
||||
| `TransistorTO92` | 363 | 0 | **none** |
|
||||
| `DiodePlastic` | 324 | 0 | **none** |
|
||||
| `RadialCeramicDiskCapacitor` | 283 | 0 | **none** |
|
||||
| `PotentiometerPanel` | 195 | 2 | **none** |
|
||||
|
||||
`RadialCeramicDiskCapacitor` is the odd one out among the capacitors — the only one of the six with
|
||||
no datasheet and no variants, despite 283 placements. `TantalumCapacitor` and `MultiSectionCapacitor`
|
||||
are likewise uncovered.
|
||||
|
||||
The opportunity is that **the datasheet mechanism is generic and nothing outside capacitors uses
|
||||
it.** `IDatasheetSupport` is a one-method interface in `diylc-core` implemented by exactly those five
|
||||
classes; see §5.3 for what extending it costs.
|
||||
|
||||
### 4.3 Hand-written labels for parts that do not exist
|
||||
|
||||
Out of 709 projects:
|
||||
|
||||
| Part | Files | Mentions | Status in library |
|
||||
|---|---|---|---|
|
||||
| Output transformer (`OT`, "output transformer") | 27 / 10 | 84 / 10 | **absent** |
|
||||
| Power transformer (`PT`, "power transformer") | 15 / 12 | 37 / 25 | **absent** |
|
||||
| Speaker | 25 | 45 | **absent**, not even a symbol |
|
||||
| Choke | 21 | 38 | **absent** |
|
||||
| 3PDT / stomp / footswitch | 12 / 6 / 5 | 12 / 8 / 8 | electrically yes, body no |
|
||||
| Push-pull pot | 12 | 15 | **absent** |
|
||||
| Standby | 10 | 31 | covered by toggle |
|
||||
| LDR | 10 | 15 | **absent** as a discrete part |
|
||||
| Reverb tank | 6 | — | **absent** |
|
||||
| 1590-series enclosure | 5 | 5 | covered — `ChassisPanel` variants |
|
||||
|
||||
Corroborating signals: 15 files assemble a transformer by hand from `TransformerCoil` pieces, and
|
||||
88 fall back to `shapes.Rectangle`.
|
||||
|
||||
**Zero hits**, and therefore Tier 3 at best: `xlr`, `binding post`, `piezo`, `jazzmaster`,
|
||||
`lipstick`, `varitone`, `concentric`, `vactrol`. The corpus is pedals and amps; its silence on
|
||||
hi-fi connectors is a property of the sample, not evidence of no demand.
|
||||
|
||||
### 4.4 Why `EyeletBoard` is used 8 times and discrete turrets 40
|
||||
|
||||
`EyeletBoard.draw()` paints a full grid of eyelets with `fillOval`. They are decorative: no control
|
||||
points, no node names, no conductive areas, nothing in the netlist. So builders ignore it and place
|
||||
individual `Turret` components, which do carry a node name, on a `BlankBoard`.
|
||||
|
||||
This kills the obvious-looking fix. A `Type = Turret` flag on `EyeletBoard` would inherit the same
|
||||
flaw, and a full grid is wrong for turret boards anyway — they are drilled per design. The real fix
|
||||
is selectable, connected positions on both boards, which is a `composite-building-blocks.md`-scale
|
||||
design question. Tier 3, §8.6.
|
||||
|
||||
## 5. Shared mechanics
|
||||
|
||||
### 5.1 Component discovery
|
||||
|
||||
Classpath annotation scan in `ComponentProcessor.getComponentTypes()`. **Any class carrying
|
||||
`@ComponentDescriptor` is picked up automatically** — no manifest, no registry. Uncommenting an
|
||||
annotation is the entire registration step.
|
||||
|
||||
### 5.2 Serialization safety
|
||||
|
||||
Per `diylc/CLAUDE.md`: never rename or delete a serialized field or change its type; fields added
|
||||
to an existing class deserialize as `null` in old files, so default in the getter, not the
|
||||
constructor; `serialVersionUID` stays `1L`.
|
||||
|
||||
Worth knowing before touching `ProjectFileManager`: its anonymous `MapperWrapper` overrides
|
||||
`shouldSerializeMember` and drops elements that map to no field, so **unknown fields in old files
|
||||
are already tolerated**. It does *not* override `realClass`, so an **unknown class throws**. That
|
||||
asymmetry is the whole of §6.1.
|
||||
|
||||
### 5.3 Variant and datasheet mechanics
|
||||
|
||||
**Variants** live in `diylc-core/src/main/resources/import-defaults/variants.xml`, an
|
||||
XStream-serialized `Map<String, List<Template>>` keyed by fully-qualified class name.
|
||||
`FactoryVariantsTest` (`diylc-library/src/test/java/org/diylc/components/`) loads it, resolves every
|
||||
key, and asserts every template names a live property and applies cleanly — so a stale variant fails
|
||||
the build rather than failing silently at runtime.
|
||||
|
||||
**Datasheets** are cheaper than they look. `DatasheetService.loadDatasheet(Class)` resolves
|
||||
`/datasheets/<SimpleClassName>.datasheet` off the classpath and splits each line on `\s*,\s*`.
|
||||
Adding one to a component is therefore two steps and no registration:
|
||||
|
||||
1. implement `IDatasheetSupport.applyModel(String[] model)` on the component, mapping columns onto
|
||||
setters — `RadialFilmCapacitor.applyModel` is 20 lines and the model to copy;
|
||||
2. drop `<ClassName>.datasheet` into `diylc-library/src/main/resources/datasheets/`.
|
||||
|
||||
`ComponentPopupMenu.updateApplyModelMenu` then builds the *Apply Model* submenu from
|
||||
`ComponentType.getDatasheet()` automatically, and `Presenter.applyModelToSelection` routes the
|
||||
chosen row back to `applyModel`.
|
||||
|
||||
Two cautions, both learned from §6.5:
|
||||
|
||||
- **The column format is positional and unforgiving.** `CapacitorDatasheetService` reads
|
||||
`model[1].split(" ")[0..1]` for voltage and `model[2].split(" ")[0..1]` for value, so a cell
|
||||
must be `"<number> <unit>"` with exactly one space. `4.7uF` throws
|
||||
`ArrayIndexOutOfBoundsException`; there is no validation anywhere.
|
||||
- **`DatasheetService` reads with `new InputStreamReader(in)` and no charset**, so files are decoded
|
||||
with the platform default. Keep datasheet files ASCII — write `uF`, never `µF`. Per JEP 400 the
|
||||
default is UTF-8 on the supported JVM, so a latin-1 byte becomes U+FFFD rather than failing loudly.
|
||||
|
||||
There is also an unused path worth knowing about: `DatasheetService.lookup` /
|
||||
`CapacitorDatasheetService.lookup` match a datasheet row by approximate value within a tolerance,
|
||||
for auto-sizing a component from its value. **It has no production caller** — only
|
||||
`CapacitorDimensionServiceTests` — so it is latent infrastructure, not live behaviour.
|
||||
|
||||
### 5.4 Factory building block mechanics
|
||||
|
||||
Building blocks are named groups of components, saved from a selection and re-placed as a unit.
|
||||
`BuildingBlockManager.importDefaultBlocks()` ships a default set in
|
||||
`diylc-core/src/main/resources/import-defaults/blocks.xml`, stored as a
|
||||
`Map<String, List<IDIYComponent<?>>>` using the `diylc` package alias
|
||||
(`aliasPackage("diylc", "org.diylc.components")`), and merges it into the user's own blocks by name.
|
||||
|
||||
Three ship today: `TO-92 w/ 3 Leads`, `TO-92 w/ 2 Leads`, `Title Block`.
|
||||
|
||||
Authoring one is a UI job, not a code job: place the components, select them, *Save as Building
|
||||
Block*, then lift the entry out of the user config into `blocks.xml`. Per D7 this is the default
|
||||
route for anything composable.
|
||||
|
||||
**The blocker (fixed in §6.6).** `importDefaultBlocks()` is wrapped in a one-shot flag:
|
||||
|
||||
```java
|
||||
if (!configManager.readBoolean(IPlugInPort.DEFAULT_BLOCKS_IMPORTED_KEY, false)) {
|
||||
```
|
||||
|
||||
Once `defaultBlocksImported` is true it never runs again, so **a factory block added to `blocks.xml`
|
||||
reaches new installs only** — never anyone who has already launched the app. Any plan that
|
||||
distributes composites this way needs §6.6 first.
|
||||
|
||||
`VariantManager.importDefaultVariants()` does not have this problem, and the asymmetry is worth
|
||||
knowing: it keeps a `defaultTemplatesImportDate` watermark, re-reads `variants.xml` on every start,
|
||||
and imports any variant whose `Template.createdOn` is newer than the watermark and whose name the
|
||||
user does not already have. New factory *variants* do reach existing users today.
|
||||
|
||||
### 5.5 Tests
|
||||
|
||||
JUnit 4 under `diylc-library/src/test/java/org/diylc/`, with `components/passive/`,
|
||||
`components/misc/` and `netlist/` already populated. Switching and netlist behaviour is unit-tested
|
||||
(`AbstractSwitchTests` and its fifteen subclasses); component rendering is not — that is the
|
||||
regression suite's job. Any new class with switching belongs in `netlist/`.
|
||||
|
||||
### 5.6 Build and verify
|
||||
|
||||
```bash
|
||||
cd diylc
|
||||
mvn -pl diylc-library -am test
|
||||
```
|
||||
|
||||
Baseline at the time of writing, on `controllers` at `2a08fbe5`: `diylc-core` 148 tests,
|
||||
`diylc-library` 331 tests, BUILD SUCCESS, project version 6.6.0.
|
||||
|
||||
Everything in Tiers 1 and 2 affects rendering or serialization, so each item also wants a
|
||||
regression run (`.claude/skills/regression-test/SKILL.md`) before it is called done. §6.1
|
||||
specifically cannot be verified any other way.
|
||||
|
||||
### 5.7 Changelog
|
||||
|
||||
User-visible additions get a `<Change>` entry in `diylc-core/src/main/resources/update.xml`, in the
|
||||
block for the unreleased version. `NEW_FEATURE` for components and variants, `BUG_FIX` for §6.1.
|
||||
|
||||
---
|
||||
|
||||
## 6. Tier 1 — no new components
|
||||
|
||||
Enum constants, catalogue data, building blocks and uncommenting. Cheapest fidelity per hour in
|
||||
the roadmap, and where D6 and D7 send most of the work.
|
||||
|
||||
§6.1, §6.5 and §6.6 are correctness fixes and should go first regardless of the rest; §6.6 also
|
||||
gates §6.7, which is the channel the composable parts travel through.
|
||||
|
||||
### 6.1 Restore the `ElectrolyticCanCapacitor` alias — **bug, not a gap**
|
||||
|
||||
`diylc-regression-data/input/cloud/diy/282.diy` contains three
|
||||
`<org.diylc.components.passive.ElectrolyticCanCapacitor>` elements. That class exists nowhere in
|
||||
the tree, in any module, or in the vendored jars, and `ProjectFileManager.configure()` registers no
|
||||
alias for it. Per §5.2 the file cannot deserialize. It came from the cloud corpus, so real user
|
||||
files are affected.
|
||||
|
||||
This is the exact failure `diylc/CLAUDE.md` forbids: a shipped component class was renamed or
|
||||
removed without an XStream alias.
|
||||
|
||||
The old element carries `name`, `alpha`, `value` (four nulls), `orientation`, `controlPoints`
|
||||
(three), `bodyColor`, `baseColor`, `borderColor`, `labelColor`, `display`, `sections` (`_1`) and
|
||||
`diameter` (1.0 in). `MultiSectionCapacitor` is the successor and matches all of it except
|
||||
`sections`, which it does not have — it derives section count from `value.length`.
|
||||
|
||||
1. In `ProjectFileManager.configure()`, beside the existing aliases:
|
||||
```java
|
||||
xStream.alias("org.diylc.components.passive.ElectrolyticCanCapacitor",
|
||||
MultiSectionCapacitor.class);
|
||||
```
|
||||
2. Confirm `ClassAliasingMapper` wins over class lookup for a dotted element name. If it does not,
|
||||
fall back to restoring a `@Deprecated` `ElectrolyticCanCapacitor` that extends
|
||||
`MultiSectionCapacitor` and carries no descriptor — invisible in the palette, resolvable by
|
||||
XStream.
|
||||
3. `sections` is dropped by the §5.2 mapper, so the file loads but renders four sections where it
|
||||
said one. Decide whether that drift is acceptable or whether it needs a converter that maps
|
||||
`sections` onto `value` length. **Recommend accepting it** — the old field disagreed with its own
|
||||
`value` array, so there is no correct answer to preserve.
|
||||
4. Verify by loading `282.diy`. A regression run is the only real check.
|
||||
|
||||
**~2 h.** Gate the rest of Tier 1 behind this.
|
||||
|
||||
### 6.2 Revive the three live dormant classes
|
||||
|
||||
Per §3.1, `AirCoreInductor`, `BoxTrimmer` and `Loadline` are finished code behind a commented
|
||||
annotation. `AirCoreInductor` needs `ComponentDescriptor`, `CreationMethod`, `IDIYComponent` and
|
||||
`SimpleComponentTransformer` (the last lives in `diylc-core`) re-imported; the other two need their
|
||||
own commented imports restored the same way.
|
||||
|
||||
Each one: uncomment, restore imports, compile, check it appears in the palette and draws. `Loadline`
|
||||
is the most interesting of the three — tube and transistor load-line plotting is a design feature
|
||||
no other item here provides, and it is already written.
|
||||
|
||||
Then decide `AxialAirCoreInductor`'s fate. It is a 70-line stub that extended `AbstractFilmCapacitor`,
|
||||
which is wrong for an inductor. **Recommend deleting the file** and treating axial air-core as a
|
||||
variant of a revived `AirCoreInductor` if it is wanted at all; a commented-out stub inheriting from
|
||||
the wrong base is worse than nothing.
|
||||
|
||||
**~3 h for three, plus a decision on the fourth.**
|
||||
|
||||
### 6.3 Tube socket bases
|
||||
|
||||
`TubeSocket.Base` covers B9A, OCTAL, B7G and B12C. `Jumbo4PinTubeSocket` is a separate class scoped
|
||||
by its own description to 211/805/845. Missing bases the amp community uses constantly:
|
||||
|
||||
| Base | Pins | Tubes |
|
||||
|---|---|---|
|
||||
| UX4 | 4 | 300B, 2A3, 45, 5U4G |
|
||||
| UX5 | 5 | type 80, 807 |
|
||||
| B8G Loctal | 8 | 7N7, 7F7 |
|
||||
| B9D Magnoval | 9 | EL509, PL519 |
|
||||
|
||||
UX4 is the valuable one and the jumbo socket does not substitute — wrong diameter, wrong spacing.
|
||||
|
||||
The class is clean to extend: `updateControlPoints()` switches on `base` for pin count, spacing and
|
||||
a `hasEmptySpace` flag, `getBody()` switches for body diameter, and `draw()` carries per-base
|
||||
keyway branches. **Both switches end in `default: throw new RuntimeException`, so a constant added
|
||||
without touching both crashes at runtime rather than failing to compile.** Add the constant, the
|
||||
`Size` constants, and a case in each switch.
|
||||
|
||||
One geometry wrinkle: UX4 and UX5 have two large filament pins and two or three small ones, so pin
|
||||
diameter becomes per-pin where today it is per-socket. Scope that before committing to UX4.
|
||||
|
||||
Dimensions must come from datasheets, not from this table. **~6-8 h for all four, UX4 first.**
|
||||
|
||||
### 6.4 Datasheets for the uncovered high-usage parts
|
||||
|
||||
Per §4.2 and D6, and **not** the capacitor work an earlier draft of this document proposed — that
|
||||
ships already. The parts with neither mechanism, in placement order:
|
||||
|
||||
| Class | Placements | What it needs |
|
||||
|---|---|---|
|
||||
| `TransistorTO92` | 363 | BC547/BC557, 2N3904/2N3906, J201, MPF102, BC108, with pinout per part |
|
||||
| `DiodePlastic` | 324 | 1N4148, 1N400x, 1N5817 |
|
||||
| `RadialCeramicDiskCapacitor` | 283 | NPO/X7R by value and voltage, with disc diameter |
|
||||
| `PotentiometerPanel` | 195 | Alpha/Bourns 16 mm and 24 mm bodies by resistance and taper |
|
||||
|
||||
`RadialCeramicDiskCapacitor` is the natural first one: it is a capacitor, so
|
||||
`CapacitorDatasheetService` already knows its column layout, and it is the only capacitor class of
|
||||
the six most-placed with no coverage at all.
|
||||
|
||||
`TransistorTO92` is the most valuable and the most work, because a transistor datasheet needs a
|
||||
pinout column that the capacitor format has no equivalent for — the existing E-C-B / E-B-C options
|
||||
would have to become a datasheet column. Scope that before starting.
|
||||
|
||||
Per §5.3 each one is an `applyModel` implementation plus a CSV. Keep every cell ASCII and every
|
||||
value `"<number> <unit>"`.
|
||||
|
||||
**~6-10 h each, `RadialCeramicDiskCapacitor` first.**
|
||||
|
||||
### 6.5 Fix six malformed datasheet rows — **bug**
|
||||
|
||||
Six of the 3,539 rows have a value cell the parser cannot read. Found by validating every row
|
||||
against `parseCapacitorDatasheet`'s own logic:
|
||||
|
||||
| File | Rows | Cell | Failure |
|
||||
|---|---|---|---|
|
||||
| `RadialElectrolytic.datasheet` | 4 (Elna Silmic II 4.7 uF at 25/35/50/63 V) | `4.7uF` | no space, so `split(" ")` yields one element and `capacitanceParts[1]` throws `ArrayIndexOutOfBoundsException` |
|
||||
| `RadialFilmCapacitor.datasheet` | 2 (WIMA MKS-4 400 V) | `0.01µF A`, `0.01µF B` | latin-1 `0xb5` in a file read with the platform charset (§5.3), no space, plus an ` A`/` B` suffix no other row has; `parseCapacitance` throws `IllegalArgumentException` |
|
||||
|
||||
User-visible effect: picking any of those six models from *Apply Model* throws instead of applying.
|
||||
|
||||
Latent effect: the same cells break `DatasheetService.lookup`, whose `Double.parseDouble` runs over
|
||||
every row in a `model|voltage` bucket. Three of the five affected buckets have the bad row first, so
|
||||
the whole bucket would throw — **80 rows across 5 buckets, including all 41 WIMA MKS-4 400 V rows**.
|
||||
That path has no production caller today (§5.3), so this is a trap set for whoever wires up
|
||||
value-based auto-sizing, not a live fault.
|
||||
|
||||
1. Rewrite the four `4.7uF` cells as `4.7 uF`.
|
||||
2. Replace the two `0.01µF A` / `0.01µF B` cells with ASCII. The ` A`/` B` suffix looks like an attempt
|
||||
to give one value two body sizes (10x3 at 7.5 mm pitch, 13x4 at 10 mm pitch) — a real need the
|
||||
positional format cannot express, since model and voltage are the whole lookup key. **Recommend
|
||||
splitting them into two models**, `WIMA MKS-4` and `WIMA MKS-4 (10mm)`, matching how
|
||||
`Epcos MKT (5mm)` / `(7.5mm)` / `(10mm)` and the WIMA 2.5 mm/5 mm entries already encode pitch in
|
||||
the model name.
|
||||
3. Add this row validation as a test beside `FactoryVariantsTest` — same shape, same
|
||||
collect-all-problems-then-assert style — so the next malformed cell fails the build. **This is
|
||||
the part that matters**; the six fixes are typos, the missing validation is why they survived.
|
||||
|
||||
**~3 h including the test.**
|
||||
|
||||
### 6.6 Make factory building blocks reach existing users — **bug, and a prerequisite**
|
||||
|
||||
Per §5.4, `importDefaultBlocks()` runs once per installation and never again, so every factory
|
||||
building block this roadmap proposes would land only on fresh installs. D7 routes most composable
|
||||
parts through that mechanism, so this is a prerequisite for §6.7, §7.5 and §8.9, not a nicety.
|
||||
|
||||
Two ways to fix it, in `BuildingBlockManager`:
|
||||
|
||||
**Recommended — drop the guard.** Delete the `DEFAULT_BLOCKS_IMPORTED_KEY` check and let the
|
||||
existing merge run on every start. The merge body already skips any name the user has
|
||||
(`if (!blocksMap.containsKey(entry.getKey()))`), so user blocks and user edits survive untouched and
|
||||
new factory names simply appear. Leave the flag being written so a downgrade still sees it.
|
||||
|
||||
One behavioural cost, which should be a deliberate choice rather than a surprise: a user who
|
||||
*deletes* a factory block gets it back on the next launch. Mitigate by recording deleted factory
|
||||
names in config if that turns out to annoy anyone — not before.
|
||||
|
||||
**Thorough — mirror the variant watermark.** Copy `VariantManager.importDefaultVariants()`: a
|
||||
`defaultBlocksImportDate` watermark plus a per-block `createdOn`. The obstacle is that `blocks.xml`
|
||||
is a `Map<String, List<IDIYComponent<?>>>` and `BuildingBlock` — which *does* have a name field but
|
||||
is not used in the stored format at all — carries no timestamp. Adding one means changing the shape
|
||||
of the `BLOCKS_KEY` config value, which also holds the user's own blocks. That is a config-format
|
||||
migration for a gain the simpler option already delivers.
|
||||
|
||||
Take the recommended option. Extend `BuildingBlockManagerTest` (8 tests, `diylc-core`) with a case
|
||||
asserting that a second `importDefaultBlocks()` on a config that already has the flag set still
|
||||
picks up a newly added default name. **~3 h.**
|
||||
|
||||
### 6.7 Ship the first factory building blocks
|
||||
|
||||
Data, not code, and per D7 this is where composable parts go. Each is authored in the app and lifted
|
||||
into `blocks.xml`. Gated on §6.6.
|
||||
|
||||
| Block | Built from | Why |
|
||||
|---|---|---|
|
||||
| Push-pull pot | `PotentiometerPanel` + `MiniToggleSwitch` (DPDT) | 12 files, 15 mentions — the highest-demand composable in the corpus |
|
||||
| Transformer symbol | `TransformerCoil` x2 + `TransformerCore` | 15 files already hand-assemble exactly this; closes most of §8.1's transformer-symbol gap for free |
|
||||
| Treble bleed | `Resistor` + `RadialFilmCapacitor` | Named network every guitar builder adds; trivial to author |
|
||||
| Tube gain stage | `TubeSocket` + grid stopper + cathode R/C + plate R | 50 corpus files place a tube socket and then rebuild this by hand every time |
|
||||
| Strat / Tele / LP harness | `guitar` pickups + pots + switch + jack | `guitar` components appear in only 8 of 425 files, which may be discoverability as much as demand |
|
||||
|
||||
Author the push-pull pot first: it is the one with measured demand, and it is the worked example that
|
||||
proves D7 before the rest follow. **~2-3 h each once §6.6 lands.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Tier 2 — new components, ranked by measured demand
|
||||
|
||||
Parts that cannot be composed from what already ships, so D7 does not apply to them. §7.6 is
|
||||
the exception and is kept only to record why it was declined.
|
||||
|
||||
### 7.1 `Transformer` — the largest single gap
|
||||
|
||||
Per D4: one class in `components.passive` extending `AbstractMultiPartComponent`, as
|
||||
`AudioTransformer` does, with a `Type` enum of Power / Output / Choke, a bell-end or open-frame
|
||||
body, and a configurable tap count per winding.
|
||||
|
||||
What the absence costs today: no BOM line, no terminals, no netlist presence, and 15 projects
|
||||
assembling one by hand out of `TransformerCoil` pieces. Every tube amp has two or three.
|
||||
|
||||
Needs: `startTrackingContinuityArea` around the tap strip, mirroring a neighbouring multi-part
|
||||
component exactly; an `IComponentTransformer` for rotation; `drawIcon()` hand-drawn for the
|
||||
toolbox; `bomPolicy = SHOW_ONLY_TYPE_NAME` with the type and tap count in the variant label.
|
||||
|
||||
A matching schematic symbol is §8.1. Ship the physical part first — the corpus demand is for
|
||||
layouts. **~16-20 h.**
|
||||
|
||||
### 7.2 `Speaker`
|
||||
|
||||
Physical component plus a schematic symbol, 25 files and 45 hand-written mentions, and the only
|
||||
item here that serves amp and hi-fi equally. Round frame with a configurable diameter, two
|
||||
terminals, an impedance property. The symbol is a few hours; the physical part is a day.
|
||||
**~10-12 h for both.**
|
||||
|
||||
### 7.3 Chassis `Choke`
|
||||
|
||||
21 files, 38 mentions. Once §7.1 exists this is largely its Choke type plus a single-winding body,
|
||||
which is the main argument for building it as part of the `Transformer` class rather than beside it.
|
||||
`RadialInductor` and `ToroidalInductor` are PCB parts and do not substitute. **~4 h if it rides on
|
||||
§7.1, ~10 h standalone.**
|
||||
|
||||
### 7.4 `Footswitch`
|
||||
|
||||
`MiniToggleSwitch` already provides 3PDT electrically, but draws as a rounded-rectangle mini toggle.
|
||||
A stomp switch is a round body with a 3x3 lug grid, and pedal layouts are built around that
|
||||
geometry. ~20 files reference 3PDT, stomp or footswitch by hand.
|
||||
|
||||
New class in `electromechanical`, reusing `ToggleSwitchType` so the netlist behaviour and the
|
||||
existing `MiniToggleSwitchTests` patterns carry over. Add a momentary SPST variant for
|
||||
non-latching relay bypass. Needs a `netlist/` test per §5.5. **~10-12 h.**
|
||||
|
||||
### 7.5 `Photoresistor`, and `Vactrol` as a building block
|
||||
|
||||
10 files, 15 mentions, and nothing discrete in the library — the only LDR class, `LDRSensorModule`,
|
||||
is a dormant breakout board rather than the bare part. Blocks tremolo, optical compressors and Uni-Vibe-style circuits.
|
||||
|
||||
Split by D7:
|
||||
|
||||
- **`Photoresistor`** is a new component. It is a two-lead primitive with its own body and no
|
||||
existing part to compose it from, so it has to be drawn. Extends `AbstractLeadedComponent`.
|
||||
- **Vactrol** is then an `LED` plus a `Photoresistor`, which is a factory building block
|
||||
(§6.7), not a class. A real vactrol is exactly those two parts in opaque heatshrink, so the
|
||||
assembly is honest.
|
||||
|
||||
**~5 h for the component, then an hour to author the block.**
|
||||
|
||||
### 7.6 Push-pull pot — **declined as a component**
|
||||
|
||||
Kept here as a record of the decision, because it is the case D7 exists for.
|
||||
|
||||
12 files and 15 mentions make this the best-evidenced composable part in the corpus, and an earlier
|
||||
draft of this roadmap proposed a `PushPullPotentiometer` class extending `AbstractPotentiometer`.
|
||||
That is the wrong answer. A push-pull pot is a `PotentiometerPanel` and a DPDT `MiniToggleSwitch`,
|
||||
both of which already draw correctly, already report the right terminals, and already have netlist
|
||||
coverage — `MiniToggleSwitchTests` alone is 15 cases. A new class would re-derive all of it and add
|
||||
a toolbox entry.
|
||||
|
||||
It ships as a factory building block instead: §6.7, first in the list.
|
||||
|
||||
The same reasoning retires the alternative that was also considered, a `switch` property on
|
||||
`PotentiometerPanel`. Besides D7, it would change the control-point count of a class placed 195
|
||||
times in the corpus, which §5.2 forbids for existing files.
|
||||
|
||||
---
|
||||
|
||||
## 8. Tier 3 — reasoned, not measured
|
||||
|
||||
Per D2 these have no corpus evidence. Ordered by how confident the judgement is, not by cost.
|
||||
|
||||
**8.1 Schematic symbol holes.** Thirty symbols with specific omissions: no speaker (§7.2), no
|
||||
momentary/push-button switch (only `SwitchLatchingSymbol`), no single transformer symbol with taps
|
||||
(only the `TransformerCoil` + `TransformerCore` primitives), no tetrode or beam-power tube and no
|
||||
dual-triode envelope for a 12AX7, no LDR, opto-isolator, thermistor, varistor, relay or SCR/Triac,
|
||||
and no crystal symbol although a physical `CrystalOscillator` ships. `GroundSymbol` offers only
|
||||
Default and Triangle with no chassis-versus-earth distinction, which matters for amp safety
|
||||
drawings. The `IC` symbol's 3- and 5-contact forms do serve as an op-amp triangle. Each is a few
|
||||
hours.
|
||||
|
||||
Two of these are not component work at all. The **transformer symbol** is `TransformerCoil` twice
|
||||
plus `TransformerCore`, so per D7 it is a factory building block (§6.7) and needs no class. The
|
||||
**opto-isolator symbol** is likewise an `LEDSymbol` facing a photoresistor symbol, once the latter
|
||||
exists. That leaves the **momentary switch** as the one genuinely missing primitive here, and the
|
||||
first worth drawing.
|
||||
|
||||
**8.2 Hi-fi connectors.** XLR (3-pin, male and female, chassis and PCB), binding post, banana,
|
||||
speakON, 5-pin DIN, BNC. Zero corpus hits, high confidence from domain knowledge, and the weakest
|
||||
domain in the library — there is no hi-fi category at all.
|
||||
|
||||
**8.3 Speaker crossover parts.** Reviving `AirCoreInductor` in §6.2 is the prerequisite and the
|
||||
cheapest hi-fi win available. Beyond it: a mains toroid (`ToroidalInductor` is an inductor, not a
|
||||
transformer with taps) and non-polar film caps at crossover values.
|
||||
|
||||
**8.4 Modern power packages.** No TO-247, TO-264 or TO-3P, so a current power amp cannot be drawn;
|
||||
vintage TO-3 ships. Plus a bolt-on heatsink — `HeatSinkResistor` is a resistor, not a heatsink.
|
||||
|
||||
**8.5 Pickups not covered.** Jazzmaster (a wide soapbar, geometrically distinct from the P-90),
|
||||
Danelectro lipstick, Rickenbacker toaster, gold foil, piezo/under-saddle. All zero corpus hits,
|
||||
which is why they sit below the Tier 2 guitar item despite looking more visible.
|
||||
|
||||
**8.6 Connected board positions.** The `EyeletBoard` problem from §4.4: selectable eyelet and
|
||||
turret positions that carry node names and reach the netlist. Benefits both boards and would
|
||||
retire the discrete-turret workaround in 40 projects. Design question, not a component.
|
||||
|
||||
**8.7 Mains safety parts.** NTC inrush thermistor, MOV, X and Y capacitors. One corpus file
|
||||
mentions NTC.
|
||||
|
||||
**8.8 Reverb tank.** Accutronics-style, 6 files. Simple geometry, narrow audience.
|
||||
|
||||
**8.9 Dual-concentric pot.** Jazz Bass, '62 Strat, Rickenbacker. Zero corpus hits, and per D7 not a
|
||||
component: two `PotentiometerPanel` instances sharing a position is a factory building block, so it
|
||||
joins the §6.7 list rather than this one once someone asks for it.
|
||||
|
||||
---
|
||||
|
||||
## 9. Sequencing
|
||||
|
||||
| Step | Items | Rough effort |
|
||||
|---|---|---|
|
||||
| 1 | §6.5 six malformed datasheet rows, plus the validation test | 3 h |
|
||||
| 2 | §6.1 alias fix, verified by regression run | 2 h |
|
||||
| 3 | §6.6 factory building block import guard | 3 h |
|
||||
| 4 | §6.7 push-pull pot block, as the D7 worked example | 3 h |
|
||||
| 5 | §6.2 revive three, decide on the fourth | 3 h |
|
||||
| 6 | §6.3 UX4, then UX5 / B8G / B9D | 6-8 h |
|
||||
| 7 | §6.4 datasheets, `RadialCeramicDiskCapacitor` first | 6-10 h each |
|
||||
| 8 | §6.7 remaining blocks: transformer symbol, treble bleed, tube gain stage, harnesses | 2-3 h each |
|
||||
| 9 | §7.1 `Transformer`, with §7.3 Choke as its third type | 20-24 h |
|
||||
| 10 | §7.2 Speaker, physical plus symbol | 10-12 h |
|
||||
| 11 | §7.4 Footswitch | 10-12 h |
|
||||
| 12 | §7.5 `Photoresistor`, then the Vactrol block | 6 h |
|
||||
|
||||
Steps 1-4 are about a day and a half: both known bugs cleared, the building block channel unblocked,
|
||||
and the highest-demand composable shipped as data rather than as a class. Step 9 is the one that
|
||||
matters most.
|
||||
|
||||
Three items that looked like components when this roadmap started are now data (§6.7, §7.6, §8.9),
|
||||
and one is split into a primitive plus a block (§7.5). That is D7 working as intended; expect the
|
||||
same question of anything added to Tier 2 later.
|
||||
|
||||
Figures are estimates for scoping, not commitments, and every dimension in this document is an
|
||||
estimate until it is checked against a datasheet (§6.3, §6.4).
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>6.6.0</version>
|
||||
<version>6.7.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>DIY Layout Creator</name>
|
||||
|
||||
Reference in New Issue
Block a user