Compare commits

...

26 Commits

Author SHA1 Message Date
Bane Stojkovic d166c58161 Copper keeps its 'Layer' property in the editor, the term PCB tools use for copper, while still counting as a Board Side component in code 2026-11-11 21:40:33 +01:00
Bane Stojkovic 144c19b44b Changelog entry for PCB text moving to the bottom page of the silkscreen print-out 2026-11-11 21:19:55 +01:00
Bane Stojkovic aef300d902 Traces, curved traces, ground fills, solder pads and PCB text call their layer 'Board Side', like every other part, and count as Board Side components in code; the saved field is still 'layer' 2026-11-11 21:15:26 +01:00
Bane Stojkovic 7ae773ab77 A part's Gerber silkscreen layer follows its Board Side in IGerberComponentSimple's default, so the exporter no longer swaps layers after the fact and GerberLayer.flip() is gone 2026-11-11 20:48:35 +01:00
Bane Stojkovic 698fba5630 Changelog: the Underside Display editing and the realistic board rendering get their own entries, and entries are ordered new features, improvements, bug fixes; CLAUDE.md records the ordering rule 2026-11-11 20:40:39 +01:00
Bane Stojkovic 34d475b18a Merge branch 'master' into copper-improvements
# Conflicts:
#	diylc/diylc-core/src/main/resources/update.xml
2026-11-11 18:39:47 +01:00
Bane Stojkovic 1fe823270e Hovering over a control point in a board's Underside Display shows its node name tooltip, as it does on the board itself 2026-11-11 18:39:29 +01:00
Bane Stojkovic 13b31e6dd5 Review fixes: a drag in the Underside Display picks the part a click would, the part being placed there is drawn like the parts already there, a bottom Dot no longer adds an empty silkscreen page; one helper decides a part's board side and copper layers share an interface 2026-11-11 17:32:46 +01:00
Bane Stojkovic 5ef6952ef4 The AI component catalog no longer offers the deprecated inner layers, and lists the new 'Board Side' property 2026-11-11 17:05:44 +01:00
Bane Stojkovic 19c78e5fa9 Parts dragged from the component tree or toolbox into a board's Underside Display go on the bottom of the board 2026-11-11 17:02:06 +01:00
Bane Stojkovic 7f8c790936 A 'Dot' used as a solder joint can be put on the bottom of the board with the 'Board Side' property 2026-11-11 16:57:35 +01:00
Bane Stojkovic 27b32b0b3c Changelog entries for the faded copper view, the true underside view and the hidden inner layers 2026-11-11 16:39:49 +01:00
Bane Stojkovic eeca9f112d New parts can be placed in a board's Underside Display; a part placed there goes on the bottom of the board 2026-11-11 15:46:29 +01:00
Bane Stojkovic 122a91a6f0 The silkscreen print-out has a separate page for parts placed on the bottom of the board 2026-11-11 15:37:27 +01:00
Bane Stojkovic 246763419c All PCB parts with a top silkscreen, including tube sockets, rotary switches, relays and transformers, have the 'Board Side' property 2026-11-11 15:34:40 +01:00
Bane Stojkovic 9721307586 Bottom-side plan: the board side is a plain property and parts are not mirrored for the user 2026-11-11 15:31:32 +01:00
Bane Stojkovic 9765f04e3c Parts placed on the bottom of the board are drawn faded on the canvas and solid in the Underside Display mirror 2026-11-11 15:20:06 +01:00
Bane Stojkovic df873df04a Resistors, capacitors, diodes, ICs, transistors and other PCB parts can be placed on the bottom of the board with the new 'Board Side' property; Gerber export puts their silkscreen on the bottom layer with the text mirrored 2026-11-11 15:15:19 +01:00
Bane Stojkovic 16f54fa479 'Solder Pad' on the top layer put its solder mask opening on the bottom layer in Gerber export 2026-11-11 15:15:10 +01:00
Bane Stojkovic b76d315f44 Components can be dragged and their points stretched through the Underside Display mirror of a board 2026-11-11 15:05:28 +01:00
Bane Stojkovic 90e10f4597 Components can be selected by clicking them in the Underside Display mirror of a board 2026-11-11 14:56:02 +01:00
Bane Stojkovic 3506c97403 A component can report which side of the board it is mounted on, and Gerber export puts the silkscreen of a part mounted underneath on the bottom layer 2026-11-11 14:48:57 +01:00
Bane Stojkovic 324cb20634 Gerber export decides each component's layer in one place, and Gerber layers can be mapped to the other side of the board 2026-11-11 14:46:02 +01:00
Bane Stojkovic 39c7743c30 Regenerated regression reference images for the far-side copper fade 2026-11-11 14:25:25 +01:00
Bane Stojkovic 0c26a71174 Merge branch 'master' into copper-improvements
# Conflicts:
#	diylc/diylc-core/src/test/java/org/diylc/presenter/DrawingManagerTest.java
2026-11-11 10:26:39 +01:00
Bane Stojkovic f73574641c phase 1 2026-11-10 22:50:16 +01:00
178 changed files with 3412 additions and 336 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 KiB

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 KiB

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 KiB

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 KiB

After

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 KiB

After

Width:  |  Height:  |  Size: 463 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 KiB

After

Width:  |  Height:  |  Size: 775 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 KiB

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 KiB

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 KiB

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 730 KiB

After

Width:  |  Height:  |  Size: 732 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 KiB

After

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 KiB

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 KiB

After

Width:  |  Height:  |  Size: 862 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 KiB

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 KiB

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 155 KiB

+8
View File
@@ -210,6 +210,14 @@ logic, netlist and analyzer behaviour, datasheet parsing, core utilities. Compon
user interaction are covered by the regression suite instead, not by unit tests. Test classes are
named `*Test` or `*Tests` and carry the license header like any other file.
## Changelog
User-facing changes are listed in `diylc-core/src/main/resources/update.xml`, one `Version` block
per release. Within a block, all `NEW_FEATURE` entries come first, then `IMPROVEMENT`, then
`BUG_FIX`; keep the existing order inside each group and add a new entry at the end of its own
group, not at the end of the block. After a merge that brings entries in from both sides, sort the
unreleased block again. Released blocks stay exactly as they shipped.
## Git
**Do not commit or push.** Make the change in the working tree and stop; the maintainer reviews and
+231 -6
View File
@@ -1,5 +1,5 @@
{
"version" : "6.10.0",
"version" : "6.11.0",
"components" : [ {
"name" : "Blank Board",
"className" : "boards.BlankBoard",
@@ -1035,7 +1035,7 @@
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8" ],
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, {
"name" : "Name",
@@ -1077,7 +1077,7 @@
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8" ],
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, {
"name" : "Name",
@@ -1196,6 +1196,11 @@
"creationMethod" : "SINGLE_CLICK",
"notes" : [ "A junction marker for schematics and wiring diagrams; it is drawing only and connects nothing, since ends that meet at the same point are already connected. For a pad on a board use Solder Pad" ],
"properties" : [ {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Color",
"type" : "Color",
"format" : "hex",
@@ -1277,7 +1282,7 @@
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8" ],
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, {
"name" : "Name",
@@ -1459,6 +1464,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -1706,7 +1716,7 @@
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8" ],
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, {
"name" : "Node Name",
@@ -7155,6 +7165,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Font Size Override",
"type" : "Integer"
@@ -7228,6 +7243,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Font Size Override",
"type" : "Integer"
@@ -7306,6 +7326,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Font Size Override",
"type" : "Integer"
@@ -7746,6 +7771,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Display",
"type" : "Display",
@@ -7851,6 +7881,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -8509,6 +8544,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Name",
"type" : "String"
@@ -8577,6 +8617,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Font Size Override",
"type" : "Integer"
@@ -8645,6 +8690,11 @@
}, {
"name" : "Angle Offset",
"type" : "Angle"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Color",
"type" : "Color",
@@ -9035,6 +9085,11 @@
}, {
"name" : "Angle",
"type" : "Angle"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Color",
"type" : "Color",
@@ -9372,6 +9427,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -9496,6 +9556,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -11842,6 +11907,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -11970,7 +12040,7 @@
}, {
"name" : "Layer",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8" ],
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_1"
}, {
"name" : "Orientation",
@@ -12082,6 +12152,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -12170,6 +12245,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -12252,6 +12332,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -12367,6 +12452,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -12492,6 +12582,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -12589,6 +12684,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -12693,6 +12793,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -12804,6 +12909,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -12913,6 +13023,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -12994,6 +13109,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -13083,6 +13203,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Coil",
"type" : "Color",
@@ -13222,6 +13347,11 @@
"type" : "Color",
"format" : "hex",
"defaultValue" : "#333333"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -13301,6 +13431,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -13459,6 +13594,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -13527,6 +13667,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -13621,6 +13766,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -13728,6 +13878,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -13786,6 +13941,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -13844,6 +14004,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -16501,6 +16666,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -16574,6 +16744,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -16837,6 +17012,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -16930,6 +17110,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Border",
"type" : "Color",
@@ -17017,6 +17202,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Color",
"type" : "Color",
@@ -17102,6 +17292,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -17263,6 +17458,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -17344,6 +17544,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -17779,6 +17984,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -18210,6 +18420,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -19072,6 +19287,11 @@
"type" : "Percentage",
"format" : "number without the % sign, e.g. 100",
"defaultValue" : "100"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Body",
"type" : "Color",
@@ -20011,6 +20231,11 @@
"type" : "Base",
"possibleValues" : [ "B9A", "OCTAL", "B7G", "B12C", "UX4" ],
"defaultValue" : "B9A"
}, {
"name" : "Board Side",
"type" : "PCBLayer",
"possibleValues" : [ "_1", "_2" ],
"defaultValue" : "_2"
}, {
"name" : "Color",
"type" : "Color",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"version" : "6.10.0",
"version" : "6.11.0",
"categories" : {
"Boards" : [ {
"name" : "Blank Board",
@@ -72,5 +72,9 @@ public enum DrawOption {
/**
* When applied, all colors will be converted to blackest black or whitest white
*/
MONOCHROMATIC;
MONOCHROMATIC,
/**
* Paint on the bottom side of a board is drawn at reduced opacity, as if seen through the board.
*/
FADE_FAR_SIDE;
}
@@ -110,6 +110,8 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
public static final String LOCKED_ALPHA = "lockedAlpha";
public static final String FADE_FAR_SIDE_KEY = "fadeFarSide";
public static final String LANGUAGE = "language";
public static final String LANGUAGE_DEFAULT = "English";
@@ -25,7 +25,11 @@ import org.diylc.core.gerber.GerberLayer;
public enum PCBLayer {
_1("Bottom"), _2("Top"), _3("Inner 1"), _4("Inner 2"), _5("Inner 3"), _6("Inner 4"), _7("Inner 5"), _8("Inner 6");
_1("Bottom"), _2("Top"),
// DIYLC models two-sided boards only. The inner layers are kept, in order, so that files using
// them still load with the same layer ids; the editor hides them and Gerber export rejects them.
@Deprecated _3("Inner 1"), @Deprecated _4("Inner 2"), @Deprecated _5("Inner 3"),
@Deprecated _6("Inner 4"), @Deprecated _7("Inner 5"), @Deprecated _8("Inner 6");
private String label;
@@ -0,0 +1,58 @@
/*
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.core;
import org.diylc.common.PCBLayer;
/**
* Interface for components on one side of a board, such as a resistor, an IC or a trace. The side
* says where the part sits physically; it is separate from {@link ILayeredComponent}, which drives
* continuity, because the leads of a through-hole part connect to copper on both sides whichever
* side the part is mounted on. Copper implements it through {@link IPCBLayerComponent}, whose layer
* is its side and also drives its continuity.
*
* @author Branislav Stojkovic
*/
public interface IBoardSideComponent {
/**
* @return {@link PCBLayer#_2} for the top of the board or {@link PCBLayer#_1} for the bottom,
* never an inner layer or null
*/
PCBLayer getBoardSide();
/**
* Sets the side of the board the component is mounted on. The component is not mirrored.
*
* @param boardSide {@link PCBLayer#_2} for the top or {@link PCBLayer#_1} for the bottom
*/
void setBoardSide(PCBLayer boardSide);
/**
* @return {@link PCBLayer#_1} for a component mounted on the bottom of the board, and
* {@link PCBLayer#_2} for any other, including components that have no side
*/
static PCBLayer sideOf(IDIYComponent<?> component) {
return component instanceof IBoardSideComponent sided && sided.getBoardSide() == PCBLayer._1
? PCBLayer._1 : PCBLayer._2;
}
}
@@ -21,6 +21,8 @@
*/
package org.diylc.core;
import org.diylc.common.PCBLayer;
/**
* Interface for tracking component drawing process. Components may use it to stop or restart
* tracking. Anything drawn while tracking is stopped will not be added to the mouse hot-spot area.
@@ -65,4 +67,26 @@ public interface IDrawingObserver {
* @param marker
*/
void setContinuityMarker(String marker);
/**
* Notifies the observer that what follows is painted on the given side of the board, until
* {@link #stopDrawingSide()}. Paint that is not bracketed is on the top (component) side. The
* observer may draw paint on the side facing away from the viewer at reduced opacity, as if seen
* through the board. Inner layers count as the bottom side.
*
* @param side the layer the paint lies on
*/
default void startDrawingSide(PCBLayer side) {}
/**
* Notifies the observer that what follows is visible from both sides of the board, such as a
* drilled hole or a cut through the copper, and is never drawn faded, until
* {@link #stopDrawingSide()}.
*/
default void startDrawingBothSides() {}
/**
* Notifies the observer that paint is back on the top (component) side.
*/
default void stopDrawingSide() {}
}
@@ -0,0 +1,52 @@
/*
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.core;
import org.diylc.common.PCBLayer;
/**
* Interface for copper drawn on a layer of a PCB, such as a trace, pad, ground fill or PCB text.
* The layer is the copper's board side, but unlike the side of a mounted part it also decides
* which other copper the copper connects to. It keeps the name 'Layer' in the editor, which is
* what PCB tools call it for copper.
*
* @author Branislav Stojkovic
*/
public interface IPCBLayerComponent extends IBoardSideComponent {
PCBLayer getLayer();
void setLayer(PCBLayer layer);
/**
* @return the layer, with the deprecated inner layers counted as the bottom
*/
@Override
default PCBLayer getBoardSide() {
return getLayer() == PCBLayer._2 ? PCBLayer._2 : PCBLayer._1;
}
@Override
default void setBoardSide(PCBLayer boardSide) {
setLayer(boardSide);
}
}
@@ -16,8 +16,10 @@ import org.apache.log4j.Logger;
import com.bancika.gerberwriter.DataLayer;
import org.diylc.common.PCBLayer;
import org.diylc.core.ComponentState;
import org.diylc.core.IBoard;
import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IView;
import org.diylc.core.Project;
@@ -145,8 +147,12 @@ public class GerberExporter {
LOG.info("Completed export to gerber");
}
private static void drawComponent(Project currentProject, GerberG2DWrapper g2d,
IDIYComponent<?> c, boolean outlineMode) {
/**
* Draws the specified component into the Gerber layers it belongs to. This is the only place that
* decides which layer a component's output goes to.
*/
static void drawComponent(Project currentProject, GerberG2DWrapper g2d,
IDIYComponent<?> c, boolean outlineMode) {
IGerberComponent gerberComponent = (IGerberComponent) c;
g2d.startedDrawingComponent();
if (gerberComponent instanceof IGerberComponentCustom) {
@@ -156,7 +162,10 @@ public class GerberExporter {
IGerberComponentSimple gerberComponentSimple = (IGerberComponentSimple) gerberComponent;
g2d.startGerberOutput(gerberComponentSimple.getGerberLayer(),
gerberComponentSimple.getGerberFunction(), gerberComponentSimple.isGerberNegative());
// text on the bottom silkscreen is read from below, so it is written mirrored
g2d.setMirrorText(IBoardSideComponent.sideOf(c) == PCBLayer._1);
c.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d);
g2d.setMirrorText(false);
g2d.stopGerberOutput();
}
}
@@ -58,6 +58,7 @@ public class GerberG2DWrapper extends Graphics2D
private double tolerance;
private Rectangle2D boardRect;
private boolean mirrorText = false;
public GerberG2DWrapper(Graphics2D graphics2d, String diylcVersion, Rectangle2D boardRect) {
super();
@@ -154,6 +155,15 @@ public class GerberG2DWrapper extends Graphics2D
return layerMap;
}
/**
* Sets whether text is written mirrored, each string about its own centre. The output is in top
* view, so text on the bottom side has to be mirrored to read correctly once the board is turned
* over.
*/
public void setMirrorText(boolean mirrorText) {
this.mirrorText = mirrorText;
}
@Override
public void draw(Shape s) {
if (trackingGerber && !getColor().equals(Constants.TRANSPARENT_COLOR)) {
@@ -181,6 +191,10 @@ public class GerberG2DWrapper extends Graphics2D
GlyphVector gv = font.createGlyphVector(graphics2d.getFontRenderContext(), str);
AffineTransform translateInstance = AffineTransform.getTranslateInstance(x, y);
if (mirrorText) {
translateInstance.translate(gv.getLogicalBounds().getWidth(), 0);
translateInstance.scale(-1, 1);
}
for (int i = 0; i < gv.getNumGlyphs(); i++) {
Shape glyphShape = gv.getGlyphOutline(i);
@@ -54,7 +54,7 @@ public enum GerberLayer {
public boolean isMirrored() {
return mirrored;
}
public String formatFileName(String fileNameBase, String boardName) {
return fileNameBase + (fileNameBase.endsWith(".") ? "" : ".")
+ boardName + "-" + getSuffix() + "." + getExtension();
@@ -25,29 +25,10 @@ import com.bancika.gerberwriter.Point;
import com.bancika.gerberwriter.path.MoveTo;
import com.bancika.gerberwriter.path.Path;
import org.diylc.core.ComponentState;
import org.diylc.core.IDIYComponent;
import org.diylc.core.Project;
import org.diylc.core.measures.SizeUnit;
public class GerberPathRenderer {
public static void drawComponent(Project currentProject, GerberG2DWrapper g2d, IDIYComponent<?> c,
boolean outlineMode) {
IGerberComponent gerberComponent = (IGerberComponent) c;
g2d.startedDrawingComponent();
if (gerberComponent instanceof IGerberComponentCustom) {
IGerberComponentCustom gerberComponentCustom = (IGerberComponentCustom) gerberComponent;
gerberComponentCustom.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d, g2d);
} else if (gerberComponent instanceof IGerberComponentSimple) {
IGerberComponentSimple gerberComponentSimple = (IGerberComponentSimple) gerberComponent;
g2d.startGerberOutput(gerberComponentSimple.getGerberLayer(),
gerberComponentSimple.getGerberFunction(), gerberComponentSimple.isGerberNegative());
c.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d);
g2d.stopGerberOutput();
}
}
public static void outputPathArea(PathIterator pathIterator, DataLayer dataLayer, double d,
boolean isNegative, String function) {
List<PathWithNegative> outputPath =
@@ -1,9 +1,18 @@
package org.diylc.core.gerber;
import org.diylc.common.PCBLayer;
import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDIYComponent;
public interface IGerberComponentSimple extends IGerberComponent {
/**
* @return the silkscreen on the side of the board the component is mounted on
*/
default GerberLayer getGerberLayer() {
return GerberLayer.SilkscreenTop;
return this instanceof IDIYComponent<?> component
&& IBoardSideComponent.sideOf(component) == PCBLayer._1
? GerberLayer.SilkscreenBot : GerberLayer.SilkscreenTop;
}
default String getGerberFunction() {
@@ -21,10 +21,9 @@
*/
package org.diylc.filter;
import java.lang.reflect.Method;
import org.diylc.common.PCBLayer;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IPCBLayerComponent;
public class PCBLayerFilter implements IComponentFilter {
@@ -37,13 +36,6 @@ public class PCBLayerFilter implements IComponentFilter {
@Override
public boolean testComponent(IDIYComponent<?> component) {
Class<?> clazz = component.getClass();
try {
Method m = clazz.getMethod("getLayer");
PCBLayer l = (PCBLayer) m.invoke(component);
return layer == l;
} catch (Exception e) {
return false;
}
return component instanceof IPCBLayerComponent copper && copper.getLayer() == layer;
}
}
@@ -1,6 +1,8 @@
package org.diylc.filter;
import org.diylc.common.PCBLayer;
import org.diylc.core.IBoard;
import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDIYComponent;
import org.diylc.core.gerber.GerberRenderMode;
import org.diylc.core.gerber.IGerberComponent;
@@ -9,6 +11,20 @@ import java.util.Set;
public class SilkScreenFilter implements IComponentFilter {
private PCBLayer side;
public SilkScreenFilter() {
this(null);
}
/**
* @param side the side of the board whose silkscreen to keep, or null for both; board outlines
* are kept on either side
*/
public SilkScreenFilter(PCBLayer side) {
this.side = side;
}
@Override
public boolean testComponent(IDIYComponent<?> component) {
if (component instanceof IBoard board) {
@@ -17,6 +33,9 @@ public class SilkScreenFilter implements IComponentFilter {
if (!(component instanceof IGerberComponent gerberComponent)) {
return false;
}
if (side != null && IBoardSideComponent.sideOf(component) != side) {
return false;
}
Set<GerberRenderMode> gerberRenderModes = gerberComponent.getGerberRenderModes();
return gerberRenderModes.contains(GerberRenderMode.Outline);
}
@@ -54,6 +54,7 @@ import org.diylc.presenter.ComparatorFactory;
import org.diylc.presenter.ComponentProcessor;
import org.diylc.presenter.DatasheetService;
import org.diylc.presenter.ReleaseHistory;
import org.diylc.utils.ReflectionUtils;
/**
* Standalone tool that generates component catalog JSON files from the current codebase.
@@ -227,9 +228,11 @@ public class ComponentCatalogGenerator {
}
}
// Include enum possible values (using name() instead of toString())
// Include enum possible values (using name() instead of toString()), leaving out the
// deprecated ones the editor does not offer either
if (propType.isEnum()) {
List<String> possibleValues = Arrays.stream(propType.getEnumConstants())
.filter(e -> !ReflectionUtils.isDeprecated((Enum<?>) e))
.map(e -> ((Enum<?>) e).name())
.collect(Collectors.toList());
prop.put("possibleValues", possibleValues);
@@ -37,6 +37,7 @@ import org.diylc.common.ComponentType;
import org.diylc.common.DrawOption;
import org.diylc.core.ComponentForRender;
import org.diylc.core.ComponentState;
import org.diylc.core.IBoardSideComponent;
import org.diylc.core.IDIYComponent;
import org.diylc.core.Project;
import org.diylc.core.annotations.ComponentDescriptor;
@@ -153,12 +154,16 @@ public class DrawingCache {
if (Math.abs(1.0 - zoom) > 1e-4) {
g2d.getCanvasGraphics().scale(1 / zoom, 1 / zoom);
}
// the image was rendered with the far-side fade already applied, so pasting it must not
// fade it again
g2d.startDrawingBothSides();
try {
// draw cached image
g2d.scale(1 / scaleFactor, 1 / scaleFactor);
g2d.getCanvasGraphics().drawImage(value.getImage(), (int) ((firstPoint.getX() - value.getDx()) * zoom * scaleFactor),
(int) ((firstPoint.getY() - value.getDy())* zoom * scaleFactor), null);
} finally {
g2d.stopDrawingSide();
if (Math.abs(1.0 - zoom) > 1e-4) {
g2d.getCanvasGraphics().scale(zoom, zoom);
}
@@ -347,6 +352,7 @@ public class DrawingCache {
// initialize wrapper
wrapper.startedDrawingComponent(zOrder);
wrapper.setUndeclaredSide(IBoardSideComponent.sideOf(component));
long componentStart = System.nanoTime();
@@ -66,7 +66,7 @@ import org.diylc.utils.Constants;
public class DrawingManager {
private static final Logger LOG = Logger.getLogger(DrawingManager.class);
public static final Percentage MIRROR_ALPHA = new Percentage(50);
public static Percentage FAR_SIDE_ALPHA = new Percentage(55);
public static int CONTROL_POINT_SIZE = 7;
public static double EXTRA_SPACE = 0.25;
@@ -118,6 +118,9 @@ public class DrawingManager {
private List<Area> proximityMarkers = null;
// the board whose underside mirror the component in the slot is being placed through
private IBoard slotMirrorBoard = null;
public DrawingManager(MessageDispatcher<EventType> messageDispatcher,
IConfigurationManager<?> configManager) {
super();
@@ -193,37 +196,12 @@ public class DrawingManager {
configureRenderingHints(g2d, drawOptions);
// group components with boards that contain them
Map<IBoard, List<IDIYComponent<?>>> boardMap = project.getComponents().stream()
.filter(IBoard.class::isInstance)
.map(IBoard.class::cast)
.filter(b -> b.getUndersideDisplay() != null && b.getUndersideDisplay() != BoardUndersideDisplay.NONE)
.collect(Collectors.toMap(x -> x, x -> new ArrayList<IDIYComponent<?>>()));
// group components with the boards whose underside mirror shows them
Map<IDIYComponent<?>, Set<IBoard>> componentBoardMap = new HashMap<IDIYComponent<?>, Set<IBoard>>();
for (IBoard board : boardMap.keySet()) {
Rectangle2D boardRect = board.getBoardRectangle();
if (boardRect == null) {
continue;
}
double minX = Math.min(boardRect.getMinX(), boardRect.getMaxX());
double maxX = Math.max(boardRect.getMinX(), boardRect.getMaxX());
double minY = Math.min(boardRect.getMinY(), boardRect.getMaxY());
double maxY = Math.max(boardRect.getMinY(), boardRect.getMaxY());
Rectangle2D normalizedBoardRect = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
List<IBoard> mirroringBoards = UndersideMirror.getMirroringBoards(project);
for (IBoard board : mirroringBoards) {
for (IDIYComponent<?> c : project.getComponents()) {
if (c == board) {
continue;
}
boolean include = false;
for (int i = 0; i < c.getControlPointCount(); i++) {
if (normalizedBoardRect.contains(c.getControlPoint(i))) {
include = true;
break;
}
}
if (include) {
boardMap.get(board).add(c);
if (c != board && UndersideMirror.isMirrored(board, c)) {
componentBoardMap.computeIfAbsent(c, key -> new HashSet<IBoard>()).add(board);
}
}
@@ -328,6 +306,8 @@ public class DrawingManager {
if (lockedComponents.contains(component) && drawOptions.contains(DrawOption.LOCKED_ALPHA)) {
g2d.setComposite(lockedComposite);
}
// after the locked composite, which is set on the canvas and would replace the fade
g2dWrapper.setUndeclaredSide(IBoardSideComponent.sideOf(component));
// Draw the component through the g2dWrapper.
long componentStart = System.nanoTime();
try {
@@ -359,6 +339,7 @@ public class DrawingManager {
}
}
ComponentArea area = g2dWrapper.finishedDrawingComponent();
g2dWrapper.setUndeclaredSide(PCBLayer._2);
// after finishedDrawingComponent, which reverts the transform the clip was taken in
if (visibleClip != null) {
g2d.setClip(visibleClip);
@@ -427,20 +408,23 @@ public class DrawingManager {
|| component.getControlPointVisibilityPolicy(i) == VisibilityPolicy.ALWAYS)) {
Point2D controlPoint = component.getControlPoint(i);
int pointSize = CONTROL_POINT_SIZE;
g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker());
g2dWrapper.fillOval((int) Math.round(controlPoint.getX() - pointSize / 2),
(int) Math.round(controlPoint.getY() - pointSize / 2), pointSize, pointSize);
g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR);
g2dWrapper.fillOval((int) Math.round(controlPoint.getX() - CONTROL_POINT_SIZE / 2 + 1),
(int) Math.round(controlPoint.getY() - CONTROL_POINT_SIZE / 2 + 1), CONTROL_POINT_SIZE - 2,
CONTROL_POINT_SIZE - 2);
drawSelectedControlPoint(g2dWrapper, controlPoint);
// the part can be selected through its mirrored copy, which then shows its points too
for (IBoard board : mirroringBoards) {
if (UndersideMirror.isMirrored(board, component)) {
drawSelectedControlPoint(g2dWrapper,
UndersideMirror.createTransform(board).transform(controlPoint, null));
}
}
}
}
}
}
// a component being placed through an underside mirror is shown there, under the cursor
AffineTransform slotTx =
slotMirrorBoard == null ? null : UndersideMirror.createTransform(slotMirrorBoard);
// Draw component slot in a separate composite.
if (componentSlot != null) {
for (IDIYComponent<?> component : componentSlot) {
@@ -448,6 +432,9 @@ public class DrawingManager {
// leaves behind (e.g. a rotated Label) is reverted before the next one is drawn
g2dWrapper.startedDrawingComponent(project.getComponents().size());
g2dWrapper.setComposite(slotComposite);
if (slotTx != null) {
viewThroughMirror(g2dWrapper, slotMirrorBoard, slotTx, component);
}
try {
component.draw(g2dWrapper, ComponentState.NORMAL,
drawOptions.contains(DrawOption.OUTLINE_MODE), project, g2dWrapper);
@@ -455,6 +442,9 @@ public class DrawingManager {
LOG.error("Error drawing " + component.getName(), e);
failedComponents.add(component);
} finally {
if (slotTx != null) {
stopViewingThroughMirror(g2dWrapper);
}
g2dWrapper.finishedDrawingComponent();
}
}
@@ -462,7 +452,9 @@ public class DrawingManager {
// Draw control points of the component in the slot.
if (controlPointSlot != null) {
for (Point2D point : controlPointSlot) {
for (Point2D slotPoint : controlPointSlot) {
Point2D point =
slotTx == null || slotPoint == null ? slotPoint : slotTx.transform(slotPoint, null);
if (point != null) {
g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker());
g2dWrapper.fillOval((int) Math.round(point.getX() - CONTROL_POINT_SIZE / 2),
@@ -553,89 +545,58 @@ public class DrawingManager {
return failedComponents;
}
private static void drawSelectedControlPoint(G2DWrapper g2dWrapper, Point2D controlPoint) {
g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker());
g2dWrapper.fillOval((int) Math.round(controlPoint.getX() - CONTROL_POINT_SIZE / 2),
(int) Math.round(controlPoint.getY() - CONTROL_POINT_SIZE / 2), CONTROL_POINT_SIZE,
CONTROL_POINT_SIZE);
g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR);
g2dWrapper.fillOval((int) Math.round(controlPoint.getX() - CONTROL_POINT_SIZE / 2 + 1),
(int) Math.round(controlPoint.getY() - CONTROL_POINT_SIZE / 2 + 1), CONTROL_POINT_SIZE - 2,
CONTROL_POINT_SIZE - 2);
}
private static void drawMirroredComponent(Project project, IBoard board, IDIYComponent<?> component,
G2DWrapper g2dWrapper, ComponentState state, boolean outlineMode) {
if (board.getUndersideDisplay() == null || board.getUndersideDisplay() == BoardUndersideDisplay.NONE) {
AffineTransform mirrorTx = UndersideMirror.createTransform(board);
if (mirrorTx == null) {
return;
}
double offset = board.getUndersideOffset() == null ? 0 : board.getUndersideOffset().convertToPixels();
Rectangle2D boardRectangle = board.getBoardRectangle();
if (boardRectangle == null) {
return;
}
double minX = Math.min(boardRectangle.getMinX(), boardRectangle.getMaxX());
double maxX = Math.max(boardRectangle.getMinX(), boardRectangle.getMaxX());
double minY = Math.min(boardRectangle.getMinY(), boardRectangle.getMaxY());
double maxY = Math.max(boardRectangle.getMinY(), boardRectangle.getMaxY());
AffineTransform mirrorTx = new AffineTransform();
switch (board.getUndersideDisplay()) {
case ABOVE: {
double pivotY = minY - offset / 2;
mirrorTx.translate(0, pivotY);
mirrorTx.scale(1.0, -1.0);
mirrorTx.translate(0, -pivotY);
break;
}
case BELOW: {
double pivotY = maxY + offset / 2;
mirrorTx.translate(0, pivotY);
mirrorTx.scale(1.0, -1.0);
mirrorTx.translate(0, -pivotY);
break;
}
case LEFT: {
double pivotX = minX - offset / 2;
mirrorTx.translate(pivotX, 0);
mirrorTx.scale(-1.0, 1.0);
mirrorTx.translate(-pivotX, 0);
break;
}
case RIGHT: {
double pivotX = maxX + offset / 2;
mirrorTx.translate(pivotX, 0);
mirrorTx.scale(-1.0, 1.0);
mirrorTx.translate(-pivotX, 0);
break;
}
default:
return;
}
G2DWrapper.MirrorDirection mirrorDirection;
switch (board.getUndersideDisplay()) {
case ABOVE:
case BELOW:
mirrorDirection = G2DWrapper.MirrorDirection.VERTICAL;
break;
case LEFT:
case RIGHT:
mirrorDirection = G2DWrapper.MirrorDirection.HORIZONTAL;
break;
default:
mirrorDirection = G2DWrapper.MirrorDirection.NONE;
break;
}
AffineTransform oldTx = g2dWrapper.getTransform();
Composite oldComposite = g2dWrapper.getComposite();
g2dWrapper.setMirrorDirection(mirrorDirection);
try {
g2dWrapper.transform(mirrorTx);
if (Boolean.TRUE.equals(board.getUndersideTransparency())) {
g2dWrapper.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)(MIRROR_ALPHA.getValue().doubleValue() / 100d)));
}
viewThroughMirror(g2dWrapper, board, mirrorTx, component);
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
} catch (Exception e) {
LOG.error("Error drawing mirrored " + component.getName(), e);
} finally {
g2dWrapper.setMirrorDirection(G2DWrapper.MirrorDirection.NONE);
stopViewingThroughMirror(g2dWrapper);
g2dWrapper.setTransform(oldTx);
g2dWrapper.setComposite(oldComposite);
}
}
/**
* Sets the wrapper up to draw the component into the board's underside mirror: reflected, with
* text kept readable, and seen from the bottom.
*/
private static void viewThroughMirror(G2DWrapper g2dWrapper, IBoard board,
AffineTransform mirrorTx, IDIYComponent<?> component) {
g2dWrapper.setMirrorDirection(UndersideMirror.getMirrorDirection(board));
g2dWrapper.transform(mirrorTx);
// the mirror is the copper side, so copper and the parts mounted on it are drawn solid and
// the component side faded
g2dWrapper.setViewedSide(PCBLayer._1, Boolean.TRUE.equals(board.getUndersideTransparency()),
component instanceof IBoard ? PCBLayer._1 : IBoardSideComponent.sideOf(component));
}
private static void stopViewingThroughMirror(G2DWrapper g2dWrapper) {
g2dWrapper.setMirrorDirection(G2DWrapper.MirrorDirection.NONE);
g2dWrapper.stopDrawingSide();
g2dWrapper.resetViewedSide();
}
private static void drawComponent(Project project, Set<DrawOption> drawOptions, Double scaleFactor,
Rectangle2D visibleRect, IDIYComponent<?> component, G2DWrapper g2dWrapper, ComponentState state,
double zoom, int i, boolean outlineMode, boolean cacheable) {
@@ -777,6 +738,16 @@ public class DrawingManager {
proximityMarkers = null;
}
/**
* Sets the board whose underside mirror the component being placed is drawn through, or null to
* draw it in the main view.
*
* @param slotMirrorBoard
*/
public void setSlotMirrorBoard(IBoard slotMirrorBoard) {
this.slotMirrorBoard = slotMirrorBoard;
}
public void clearContinuityArea() {
currentContinuityAreas = null;
continuityGraphCache = null;
@@ -796,6 +767,50 @@ public class DrawingManager {
return components;
}
/**
* Finds the components that the underside mirror of a board shows at the specified point, top
* first, or none when the point is not in a mirror.
*
* @param point
* @param project
* @return
*/
public List<IDIYComponent<?>> findMirroredComponentsAt(Point2D point, Project project) {
List<IDIYComponent<?>> components = new ArrayList<IDIYComponent<?>>();
for (IBoard board : UndersideMirror.findBoardsMirroredAt(project, point)) {
for (IDIYComponent<?> component : findMirroredComponentsAt(point, project, board)) {
if (!components.contains(component)) {
components.add(component);
}
}
}
return components;
}
/**
* Finds the components that the underside mirror of the specified board shows at the specified
* point, top first, or none when the point is not in that mirror.
*
* @param point
* @param project
* @param board
* @return
*/
public List<IDIYComponent<?>> findMirroredComponentsAt(Point2D point, Project project,
IBoard board) {
List<IDIYComponent<?>> components = new ArrayList<IDIYComponent<?>>();
AffineTransform mirrorTx = UndersideMirror.createTransform(board);
if (mirrorTx == null || !UndersideMirror.getMirrorRegion(board).contains(point)) {
return components;
}
for (IDIYComponent<?> component : findComponentsAt(mirrorTx.transform(point, null), project)) {
if (UndersideMirror.isMirrored(board, component)) {
components.add(component);
}
}
return components;
}
public double getExtraSpace(Project project) {
double width = project.getWidth().convertToPixels();
double height = project.getHeight().convertToPixels();

Some files were not shown because too many files have changed in this diff Show More