Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d166c58161 | |||
| 144c19b44b | |||
| aef300d902 | |||
| 7ae773ab77 | |||
| 698fba5630 | |||
| 34d475b18a | |||
| 1fe823270e | |||
| 13b31e6dd5 | |||
| 5ef6952ef4 | |||
| 19c78e5fa9 | |||
| 7f8c790936 | |||
| 27b32b0b3c | |||
| eeca9f112d | |||
| 122a91a6f0 | |||
| 246763419c | |||
| 9721307586 | |||
| 9765f04e3c | |||
| df873df04a | |||
| 16f54fa479 | |||
| b76d315f44 | |||
| 90e10f4597 | |||
| 3506c97403 | |||
| 324cb20634 | |||
| 39c7743c30 | |||
| 0c26a71174 | |||
| f73574641c |
|
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 231 KiB |
|
Before Width: | Height: | Size: 375 KiB After Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 293 KiB After Width: | Height: | Size: 312 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 286 KiB After Width: | Height: | Size: 286 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 433 KiB After Width: | Height: | Size: 432 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 601 KiB After Width: | Height: | Size: 600 KiB |
|
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 229 KiB |
|
Before Width: | Height: | Size: 463 KiB After Width: | Height: | Size: 463 KiB |
|
Before Width: | Height: | Size: 775 KiB After Width: | Height: | Size: 775 KiB |
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 359 KiB |
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 350 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 805 KiB After Width: | Height: | Size: 966 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 721 KiB After Width: | Height: | Size: 722 KiB |
|
Before Width: | Height: | Size: 506 KiB After Width: | Height: | Size: 531 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 231 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 3.6 MiB After Width: | Height: | Size: 3.7 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 730 KiB After Width: | Height: | Size: 732 KiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 593 KiB After Width: | Height: | Size: 594 KiB |
|
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 13 MiB After Width: | Height: | Size: 13 MiB |
|
Before Width: | Height: | Size: 410 KiB After Width: | Height: | Size: 410 KiB |
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 398 KiB After Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 862 KiB After Width: | Height: | Size: 862 KiB |
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 314 KiB |
|
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 233 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 246 KiB After Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 229 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 3.8 MiB After Width: | Height: | Size: 3.8 MiB |
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 350 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 220 KiB |
|
Before Width: | Height: | Size: 582 KiB After Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 285 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 235 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 318 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 319 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 155 KiB |
@@ -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
|
||||
|
||||
@@ -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,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();
|
||||
|
||||