chore: update deployed documentation

This commit is contained in:
jamork committed 2025-09-08 06:48:04 +00:00
1 parent a5e5a3b96d
commit 651541da82
1694 files changed
+4099896 -497061

No files matched your search

+537
View File
@@ -0,0 +1,537 @@
0
BRDOUT: 13 8631 4287
8630 2829
8631 0
5240 0
5239 1988
5499 1988
5499 2011
5482 2011
5482 2328
5500 2328
5500 2352
5239 2352
5239 2830
8630 2829
NETS: 86
1 GND
2 V_USB
3 15V
4 5V
5 9VAC2
6 9VAC1
7 V_REF = 2.048 | 2.5 | 2.9
8 9VAC_OUT2
9 9VAC_OUT1
10 3.3V
11 VDDA
12 Net-(IC1-V5V)
13 Net-(D1-A)
14 Net-(IC1-VOUT)
15 Net-(IC1-VSEL)
16 unconnected-(IC1-FLIP-Pad6)
17 DN
18 Net-(IC1-PWR_EN)
19 Net-(IC1-LED)
20 Net-(IC1-VCC)
21 Net-(IC1-ISEL)
22 DP
23 CC1
24 CC2
25 unconnected-(J1-SBU1-PadA8)
26 unconnected-(J1-SBU2-PadB8)
27 NRST
28 JTDI
29 VCP_TX
30 unconnected-(J3-NC-Pad2)
31 JTMS
32 unconnected-(J3-NC-Pad1)
33 unconnected-(J3-JRCLK{slash}NC-Pad9)
34 VCP_RX
35 JTDO
36 Net-(Q1-Pad4)
37 Net-(Q1-Pad5_8)
38 G3
39 G4
40 G1
41 G2
42 Net-(U3-+)
43 Net-(U3--)
44 1.225V
45 OSC_OUT
46 PWM_G1
47 PWM_G2
48 PWM_G4
49 PWM_G3
50 unconnected-(U2-PA1-Pad9)
51 unconnected-(U2-PB9-Pad46)
52 unconnected-(U2-PC15-Pad4)
53 OSC_IN
54 unconnected-(U2-PA3-Pad11)
55 unconnected-(U2-PB4-Pad41)
56 unconnected-(U2-PB15-Pad29)
57 unconnected-(U2-PB0-Pad16)
58 unconnected-(U2-PB2-Pad18)
59 unconnected-(U2-PB5-Pad42)
60 9VAC_COMP_ADC
61 unconnected-(U2-PA2-Pad10)
62 unconnected-(U2-PA7-Pad15)
63 JTCLK
64 unconnected-(U2-PG10-Pad7)
65 unconnected-(U2-PA0-Pad8)
66 unconnected-(U2-PA6-Pad14)
67 unconnected-(U2-PC13-Pad2)
68 unconnected-(U2-PA12-Pad34)
69 unconnected-(U2-PB8-Pad45)
70 unconnected-(U2-PA5-Pad13)
71 unconnected-(U2-PB1-Pad17)
72 unconnected-(U2-PB10-Pad22)
73 unconnected-(U2-PC14-Pad3)
74 unconnected-(U2-PA4-Pad12)
75 ENABLE_DRVA
76 ENABLE_DRVB
77 CURRENT_COMP_ADC
78 unconnected-(U7-NC-Pad3)
79 unconnected-(U8-NC-Pad4)
80 Net-(L2-Pad1)
81 Net-(L3-Pad1)
82 Net-(IC2-SW)
83 Net-(IC2-VBST)
84 Net-(IC2-EN)
85 Net-(IC2-VFB)
86 Net-(U1-+)
PARTS: 90
C1 5619 2158 5747 1975 0 1
C10 6278 2323 6406 2140 2 1
C104 5703 1760 5777 1722 4 1
C11 6621 2033 6695 1995 6 1
C142 5968 1141 6041 1103 8 1
C19 7295 3231 7388 3414 10 2
C2 6800 831 6885 758 12 1
C20 5756 447 5829 409 14 1
C21 7434 3192 7473 3265 16 2
C22 7474 3183 7513 3256 18 2
C23 7820 1824 7913 2007 20 2
C24 6886 832 6971 758 22 1
C25 7059 3230 7151 3413 24 2
C27 6282 1186 6355 1147 26 1
C28 6652 1009 6690 936 28 1
C29 6281 1147 6355 1109 30 1
C3 6280 2119 6408 1936 32 1
C30 6751 1095 6825 1057 34 1
C31 6188 1315 6371 1187 36 1
C33 6186 1444 6369 1316 38 1
C35 5969 1376 6007 1302 40 1
C36 5755 485 5829 447 42 1
C37 6884 2357 7020 2278 44 1
C38 6868 2273 7004 2194 46 1
C39 6732 2273 6868 2194 48 1
C4 6868 2193 7004 2114 50 1
C46 5896 1141 5969 1103 52 1
C47 5620 931 5694 893 54 1
C48 5830 447 5904 409 56 1
C49 5830 485 5904 447 58 1
C5 6883 2605 7019 2526 60 1
C59 5912 537 5986 499 62 1
C6 6884 2521 7020 2442 64 1
C60 5952 888 5990 815 66 1
C61 5544 854 5582 781 68 1
C62 5889 1376 5927 1303 70 1
C63 5968 1103 6042 1065 72 1
C7 6884 2439 7020 2360 74 1
C8 6048 2053 6231 1925 76 1
C9 6597 2267 6671 2228 78 1
C94 5468 649 5546 570 80 1
C97 5408 809 5486 730 82 1
D1 5682 1975 5802 1913 84 1
D2 7672 1781 7873 2061 86 2
D27 5793 370 5923 225 88 1
D3 5589 2480 5868 2278 90 1
H1 7359 2754 7601 2512 92 1
H2 5390 2754 5632 2512 95 1
H3 5390 274 5632 32 98 1
IC1 5695 1907 5864 1756 101 1
IC2 6554 2221 6721 2079 116 1
J1 5215 2423 5562 1616 122 1
J2 7862 2811 8612 1975 138 1
J3 5229 1384 5861 1136 149 1
L1 6427 2706 6880 2282 163 1
L2 6579 1691 6998 1134 165 1
L3 6436 564 6993 145 167 1
Q1 6101 2253 6287 2094 169 1
Q2 6438 1110 6652 797 174 1
Q5 5915 2254 6101 2094 182 1
R1 5723 2218 5841 2158 187 1
R2 7747 1571 7872 1696 189 2
R21 6693 670 6827 593 191 1
R22 7604 3208 7663 3327 193 2
R23 5830 409 5905 370 195 1
R24 7604 3328 7663 3447 197 2
R25 7664 3268 7783 3327 199 2
R26 7724 3148 7783 3266 201 2
R27 7462 3396 7581 3455 203 2
R28 7759 2081 7884 2206 205 2
R29 6734 2187 6773 2112 207 1
R3 5948 2051 5987 1976 209 1
R30 6734 2111 6773 2036 211 1
R37 5948 2091 6023 2052 213 1
R4 5806 2036 5866 1918 215 1
R5 5427 1914 5545 1854 217 1
R6 5426 1854 5545 1795 219 1
R7 5882 1787 6001 1728 221 1
R71 5756 409 5831 370 223 1
R8 6414 2089 6474 1971 225 1
R9 6620 2075 6695 2036 227 1
T1 6999 1944 8620 8 229 1
U1 6685 815 6848 672 239 1
U2 5584 893 5991 486 244 1
U3 7434 3253 7597 3395 292 2
U4 6689 1055 6982 835 297 1
U5 6202 1109 6422 816 305 1
U7 7787 3200 7940 3342 313 2
U8 5879 1303 6022 1140 316 1
Y2 5396 791 5579 608 321 1
PINS: 325
5683 2125 2 1
5683 2009 1 1
6342 2290 1 1
6342 2174 3 1
5759 1741 1 1
5721 1741 12 1
6677 2014 1 1
6639 2014 84 1
5985 1122 10 1
6023 1122 1 1
7342 3264 6 2
7342 3380 5 2
6866 813 1 1
6866 776 10 1
5811 428 7 1
5774 428 1 1
7454 3210 1 2
7454 3248 4 2
7494 3238 4 2
7494 3201 1 2
7866 1858 9 2
7866 1974 8 2
6905 776 10 1
6905 814 1 1
7105 3263 6 2
7105 3380 5 2
6300 1166 1 1
6337 1166 3 1
6671 954 1 1
6671 991 3 1
6337 1128 3 1
6299 1128 1 1
6344 2085 3 1
6344 1969 1 1
6807 1076 3 1
6769 1076 1 1
6221 1251 1 1
6337 1251 3 1
6336 1380 3 1
6220 1380 1 1
5988 1320 4 1
5988 1358 1 1
5811 466 7 1
5773 466 1 1
6914 2318 4 1
6989 2318 1 1
6898 2234 4 1
6973 2234 1 1
6837 2234 4 1
6762 2234 1 1
6898 2154 4 1
6973 2154 1 1
5951 1122 10 1
5913 1122 1 1
5638 912 1 1
5676 912 10 1
5848 428 11 1
5886 428 1 1
5886 466 1 1
5848 466 11 1
6914 2566 4 1
6989 2566 1 1
5930 518 10 1
5968 518 1 1
6914 2482 4 1
6989 2482 1 1
5971 833 10 1
5971 870 1 1
5563 799 10 1
5563 836 1 1
5908 1321 4 1
5908 1359 1 1
5986 1084 10 1
6024 1084 1 1
6914 2400 4 1
6989 2400 1 1
6081 1989 1 1
6197 1989 3 1
6615 2248 82 1
6653 2248 83 1
5494 596 1 1
5521 623 45 1
5434 756 1 1
5461 783 53 1
5712 1944 1 1
5774 1944 13 1
7742 1999 8 2
7742 1842 9 2
5899 332 10 1
5816 332 7 1
5650 2348 2 1
5808 2348 1 1
7480 2633 1 1
7480 2633 1 1
7480 2633 1 1
5511 2633 1 1
5511 2633 1 1
5511 2633 1 1
5511 153 1 1
5511 153 1 1
5511 153 1 1
5839 1789 14 1
5839 1805 18 1
5839 1821 20 1
5839 1837 2 1
5839 1852 1 1
5839 1868 16 1
5839 1884 19 1
5721 1884 15 1
5721 1868 21 1
5721 1852 24 1
5721 1837 23 1
5721 1821 17 1
5721 1805 22 1
5721 1789 12 1
5780 1837 1 1
6589 2187 1 1
6589 2150 82 1
6589 2112 3 1
6687 2112 85 1
6687 2150 84 1
6687 2187 83 1
5511 2296 1 1
5511 2264 2 1
5511 2219 23 1
5511 2180 22 1
5511 2160 17 1
5511 2121 25 1
5511 2044 1 1
5511 2075 2 1
5511 2101 24 1
5511 2140 22 1
5511 2199 17 1
5511 2239 26 1
5466 2391 1 1
5466 1949 1 1
5309 2391 1 1
5309 1949 1 1
8503 2281 1 1
8503 2478 1 1
8109 2577 1 1
8109 2380 1 1
8109 2183 1 1
8011 2478 4 1
8011 2281 4 1
8011 2675 9 1
8011 2085 8 1
8306 2085 0 1
8306 2675 0 1
5695 1336 32 1
5695 1183 30 1
5645 1336 10 1
5645 1183 31 1
5595 1336 1 1
5595 1183 63 1
5545 1336 1 1
5545 1183 35 1
5495 1336 33 1
5495 1183 28 1
5445 1336 1 1
5445 1183 27 1
5395 1336 34 1
5395 1183 29 1
6503 2494 82 1
6804 2494 4 1
6788 1226 80 1
6788 1600 6 1
6527 354 81 1
6901 354 5 1
6244 2135 3 1
6244 2161 3 1
6244 2186 3 1
6244 2212 36 1
6185 2173 37 1
6470 865 39 1
6520 865 81 1
6570 865 86 1
6620 865 41 1
6620 1060 40 1
6570 1060 80 1
6520 1060 3 1
6470 1060 38 1
5959 2212 20 1
5959 2187 20 1
5959 2161 20 1
5959 2135 36 1
6018 2174 37 1
5749 2188 2 1
5814 2188 20 1
7787 1611 1 2
7832 1656 9 2
6724 632 86 1
6796 632 1 1
7634 3300 42 2
7634 3235 6 2
5887 390 10 1
5847 390 11 1
7634 3355 43 2
7634 3420 5 2
7691 3298 42 2
7756 3298 44 2
7754 3240 44 2
7754 3175 10 2
7554 3426 43 2
7489 3426 60 2
7844 2121 8 2
7799 2166 1 2
6754 2170 4 1
6754 2130 85 1
5968 2034 36 1
5968 1994 18 1
6754 2094 85 1
6754 2054 1 1
5966 2072 36 1
6006 2072 37 1
5836 1945 19 1
5836 2010 13 1
5518 1884 15 1
5453 1884 1 1
5518 1824 21 1
5453 1824 1 1
5974 1757 3 1
5909 1757 14 1
5813 390 7 1
5773 390 1 1
6444 2062 3 1
6444 1997 84 1
6638 2056 84 1
6678 2056 1 1
7219 1566 6 1
7219 1271 0 1
7219 976 0 1
7219 680 0 1
7219 385 5 1
8400 1566 8 1
8400 1271 0 1
8400 976 0 1
8400 680 0 1
8400 385 9 1
6722 777 77 1
6722 740 1 1
6722 703 86 1
6811 703 1 1
6811 777 10 1
5623 798 10 1
5623 778 67 1
5623 758 73 1
5623 739 52 1
5623 719 53 1
5623 699 45 1
5623 680 64 1
5623 660 65 1
5623 640 50 1
5623 621 61 1
5623 601 54 1
5623 581 74 1
5679 526 70 1
5699 526 66 1
5718 526 62 1
5738 526 57 1
5758 526 71 1
5777 526 58 1
5797 526 1 1
5817 526 7 1
5837 526 11 1
5856 526 72 1
5876 526 1 1
5896 526 10 1
5951 581 75 1
5951 601 76 1
5951 621 60 1
5951 640 77 1
5951 660 56 1
5951 680 49 1
5951 699 48 1
5951 719 46 1
5951 739 47 1
5951 758 68 1
5951 778 1 1
5951 798 10 1
5896 853 31 1
5876 853 63 1
5856 853 28 1
5837 853 35 1
5817 853 55 1
5797 853 59 1
5777 853 29 1
5758 853 34 1
5738 853 69 1
5718 853 51 1
5699 853 1 1
5679 853 10 1
7560 3290 42 2
7560 3328 1 2
7560 3365 43 2
7471 3365 60 2
7471 3290 4 2
6933 872 75 1
6933 922 46 1
6933 972 1 1
6933 1022 47 1
6738 1022 40 1
6738 972 3 1
6738 922 41 1
6738 872 76 1
6239 865 75 1
6289 865 49 1
6339 865 1 1
6389 865 48 1
6389 1060 38 1
6339 1060 3 1
6289 1060 39 1
6239 1060 76 1
7827 3305 44 2
7827 3230 1 2
7900 3268 78 2
5984 1266 4 1
5947 1266 1 1
5910 1266 4 1
5910 1177 79 1
5984 1177 10 1
5482 656 45 1
5531 704 1 1
5492 743 53 1
5443 695 1 1
NAILS: 12
1 6067 1014 47 1
1 6067 914 1 1
2 6161 979 46 1
2 6161 1079 1 1
3 6294 627 49 1
3 6294 527 1 1
4 6200 723 48 1
4 6300 723 1 1
6 7622 2312 8 1
6 7622 2412 9 1
7 6857 2068 4 1
7 6957 2068 1 1
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+531
View File
@@ -0,0 +1,531 @@
// Generated by SolidPython <Unknown> on 2025-09-08 06:43:20
$fa = 0.4; $fs = 0.4;
rotate(a = 0, v = [1, 0, 0]) {
difference() {
scale(v = [1, 1, 1]) {
difference() {
linear_extrude(height = 1.4500000000) {
offset(r = 1.0000000000) {
import(file = "c64psu-stencil_3d_edge.dxf", origin = [0, 0]);
}
}
translate(v = [0, 0, 0.1500000000]) {
linear_extrude(height = 1.4500000000) {
translate(v = [0, 0, 0]) {
import(file = "c64psu-stencil_3d_edge.dxf", origin = [0, 0]);
}
}
}
}
}
linear_extrude(center = true, height = 0.6000000000) {
translate(v = [0, 0, 0]) {
import(file = "c64psu-stencil_3d_bottom.dxf", origin = [0, 0]);
}
}
}
}
/***********************************************
********* SolidPython code: **********
************************************************
from pcbnewTransition import pcbnew
import numpy as np
import json
from collections import OrderedDict
from kikit.common import *
from kikit.defs import *
from kikit.substrate import Substrate, extractRings, toShapely, linestringToKicad
from kikit.export import gerberImpl, pasteDxfExport
from kikit.export import exportSettingsJlcpcb
# Allow importing solid from this dir
import os
import sys
cur_path = os.path.abspath(os.path.dirname(__file__))
if cur_path not in sys.path:
sys.path.insert(0, cur_path)
import solid
import solid.utils
import subprocess
import shutil
from kikit.common import removeComponents, parseReferences
from shapely.geometry import Point
OUTER_BORDER = fromMm(7.5)
INNER_BORDER = fromMm(5)
MOUNTING_HOLES_COUNT = 3
MOUNTING_HOLE_R = fromMm(1)
HOLE_SPACING = fromMm(20)
def addBottomCounterpart(board, item):
item = item.Duplicate()
item.SetLayer(Layer.B_Paste)
board.Add(item)
def addRoundedCorner(board, center, start, end, thickness):
corner = pcbnew.PCB_SHAPE()
corner.SetShape(STROKE_T.S_ARC)
corner.SetCenter(toKiCADPoint((center[0], center[1])))
corner.SetStart(toKiCADPoint((start[0], start[1])))
if np.cross(start - center, end - center) > 0:
corner.SetArcAngleAndEnd(fromDegrees(90), True)
else:
corner.SetArcAngleAndEnd(fromDegrees(-90), True)
corner.SetWidth(thickness)
corner.SetLayer(Layer.F_Paste)
board.Add(corner)
addBottomCounterpart(board, corner)
def addLine(board, start, end, thickness):
line = pcbnew.PCB_SHAPE()
line.SetShape(STROKE_T.S_SEGMENT)
line.SetStart(toKiCADPoint((start[0], start[1])))
line.SetEnd(toKiCADPoint((end[0], end[1])))
line.SetWidth(thickness)
line.SetLayer(Layer.F_Paste)
board.Add(line)
addBottomCounterpart(board, line)
def addBite(board, origin, direction, normal, thickness):
"""
Adds a bite to the stencil, direction points to the bridge, normal points
inside the stencil
"""
direction = normalize(direction) * thickness
normal = normalize(normal) * thickness
center = toKiCADPoint((origin[0], origin[1])) + toKiCADPoint((normal[0], normal[1]))
start = origin
end = center + toKiCADPoint((direction[0], direction[1]))
# addLine(board, end, end + normal / 2, thickness)
addRoundedCorner(board, center, start, end, thickness)
def numberOfCuts(length, bridgeWidth, bridgeSpacing):
"""
Return number of bridges which fit inside the length and cut length
"""
count = round(np.floor((length + bridgeWidth) / (bridgeWidth + bridgeSpacing)))
cutLength = round((length - (count - 1) * bridgeWidth) / count)
return count, cutLength
def addFrame(board, rect, bridgeWidth, bridgeSpacing, clearance):
"""
Add rectangular frame to the board
"""
R=fromMm(1)
corners = [
(tl(rect), toKiCADPoint((R, 0)), toKiCADPoint((0, R))), # TL
(tr(rect), toKiCADPoint((0, R)), toKiCADPoint((-R, 0))), # TR
(br(rect), toKiCADPoint((-R, 0)), toKiCADPoint((0, -R))), # BR
(bl(rect), toKiCADPoint((0, -R)), toKiCADPoint((R, 0))) # BL
]
for c, sOffset, eOffset in corners:
addRoundedCorner(board, c + sOffset + eOffset, c + sOffset, c + eOffset, clearance)
count, cutLength = numberOfCuts(rect.GetWidth() - 2 * R, bridgeWidth, bridgeSpacing)
for i in range(count):
start = round(rect.GetX() + R + i * bridgeWidth + i * cutLength)
end = start + cutLength
y1, y2 = rect.GetY(), rect.GetY() + rect.GetHeight()
addLine(board, toKiCADPoint((start, y1)), toKiCADPoint((end, y1)), clearance)
if i != 0:
addBite(board, toKiCADPoint((start, y1)), toKiCADPoint((-1, 0)), toKiCADPoint((0, 1)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((end, y1)), toKiCADPoint((1, 0)), toKiCADPoint((0, 1)), clearance)
addLine(board, toKiCADPoint((start, y2)), toKiCADPoint((end, y2)), clearance)
if i != 0:
addBite(board, toKiCADPoint((start, y2)), toKiCADPoint((-1, 0)), toKiCADPoint((0, -1)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((end, y2)), toKiCADPoint((1, 0)), toKiCADPoint((0, -1)), clearance)
count, cutLength = numberOfCuts(rect.GetHeight() - 2 * R, bridgeWidth, bridgeSpacing)
for i in range(count):
start = rect.GetY() + R + i * bridgeWidth + i * cutLength
end = start + cutLength
x1, x2 = rect.GetX(), rect.GetX() + rect.GetWidth()
addLine(board, toKiCADPoint((x1, start)), toKiCADPoint((x1, end)), clearance)
if i != 0:
addBite(board, toKiCADPoint((x1, start)), toKiCADPoint((0, -1)), toKiCADPoint((1, 0)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((x1, end)), toKiCADPoint((0, 1)), toKiCADPoint((1, 0)), clearance)
addLine(board, toKiCADPoint((x2, start)), toKiCADPoint((x2, end)), clearance)
if i != 0:
addBite(board, toKiCADPoint((x2, start)), toKiCADPoint((0, -1)), toKiCADPoint((-1, 0)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((x2, end)), toKiCADPoint((0, 1)), toKiCADPoint((-1, 0)), clearance)
def addHole(board, position, radius):
circle = pcbnew.PCB_SHAPE()
circle.SetShape(STROKE_T.S_CIRCLE)
circle.SetCenter(toKiCADPoint((position[0], position[1])))
# Set 3'oclock point of the circle to set radius
circle.SetEnd(toKiCADPoint((position[0], position[1])) + toKiCADPoint((radius/2, 0)))
circle.SetWidth(radius)
circle.SetLayer(Layer.F_Paste)
board.Add(circle)
addBottomCounterpart(board, circle)
def addJigFrame(board, jigFrameSize, bridgeWidth=fromMm(2),
bridgeSpacing=fromMm(10), clearance=fromMm(0.5)):
"""
Given a Pcbnew board finds the board outline and creates a stencil for
KiKit's stencil jig.
Mainly, adds mounting holes and mouse bites to define the panel outline.
jigFrameSize is a tuple (width, height).
"""
bBox = findBoardBoundingBox(board)
frameSize = rectByCenter(rectCenter(bBox),
jigFrameSize[0] + 2 * (OUTER_BORDER + INNER_BORDER),
jigFrameSize[1] + 2 * (OUTER_BORDER + INNER_BORDER))
cutSize = rectByCenter(rectCenter(bBox),
jigFrameSize[0] + 2 * (OUTER_BORDER + INNER_BORDER) - fromMm(1),
jigFrameSize[1] + 2 * (OUTER_BORDER + INNER_BORDER) - fromMm(1))
addFrame(board, cutSize, bridgeWidth, bridgeSpacing, clearance)
for i in range(MOUNTING_HOLES_COUNT):
x = frameSize.GetX() + OUTER_BORDER / 2 + (i + 1) * (frameSize.GetWidth() - OUTER_BORDER) / (MOUNTING_HOLES_COUNT + 1)
addHole(board, toKiCADPoint((x, OUTER_BORDER / 2 + frameSize.GetY())), MOUNTING_HOLE_R)
addHole(board, toKiCADPoint((x, - OUTER_BORDER / 2 +frameSize.GetY() + frameSize.GetHeight())), MOUNTING_HOLE_R)
for i in range(MOUNTING_HOLES_COUNT):
y = frameSize.GetY() + OUTER_BORDER / 2 + (i + 1) * (frameSize.GetHeight() - OUTER_BORDER) / (MOUNTING_HOLES_COUNT + 1)
addHole(board, toKiCADPoint((OUTER_BORDER / 2 + frameSize.GetX(), y)), MOUNTING_HOLE_R)
addHole(board, toKiCADPoint((- OUTER_BORDER / 2 +frameSize.GetX() + frameSize.GetWidth(), y)), MOUNTING_HOLE_R)
PIN_TOLERANCE = fromMm(0.05)
addHole(board, tl(frameSize) + toKiCADPoint((OUTER_BORDER / 2, OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, tr(frameSize) + toKiCADPoint((-OUTER_BORDER / 2, OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, br(frameSize) + toKiCADPoint((-OUTER_BORDER / 2, -OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, bl(frameSize) + toKiCADPoint((OUTER_BORDER / 2, -OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
def jigMountingHoles(jigFrameSize, origin=toKiCADPoint((0, 0))):
""" Get list of all mounting holes in a jig of given size """
w, h = jigFrameSize
holes = [
toKiCADPoint((0, (w + INNER_BORDER) / 2)),
toKiCADPoint((0, -(w + INNER_BORDER) / 2)),
toKiCADPoint(((h + INNER_BORDER) / 2, 0)),
toKiCADPoint((-(h + INNER_BORDER) / 2, 0)),
]
return [x + origin for x in holes]
def createOuterPolygon(board, jigFrameSize, outerBorder):
bBox = findBoardBoundingBox(board)
centerpoint = rectCenter(bBox)
holes = jigMountingHoles(jigFrameSize, centerpoint)
outerSubstrate = Substrate(collectEdges(board, Layer.Edge_Cuts))
outerSubstrate.substrates = outerSubstrate.substrates.buffer(outerBorder)
tabs = []
for hole in holes:
tab, _ = outerSubstrate.tab(hole, centerpoint - hole, INNER_BORDER, maxHeight=fromMm(1000))
tabs.append(tab)
outerSubstrate.union(tabs)
outerSubstrate.union([Point(x).buffer(INNER_BORDER / 2) for x in holes])
outerSubstrate.millFillets(fromMm(3))
return outerSubstrate.exterior(), holes
def createOffsetPolygon(board, offset):
outerSubstrate = Substrate(collectEdges(board, Layer.Edge_Cuts))
outerSubstrate.substrates = outerSubstrate.substrates.buffer(offset)
return outerSubstrate.exterior()
def m2countersink():
HEAD_DIA = fromMm(4.5)
HOLE_LEN = fromMm(10)
SINK_EXTRA = fromMm(0.3)
sinkH = np.sqrt(HEAD_DIA**2 / 4)
sink = solid.cylinder(d1=0, d2=HEAD_DIA, h=sinkH)
sinkE = solid.cylinder(d=HEAD_DIA, h=SINK_EXTRA)
hole = solid.cylinder(h=HOLE_LEN, d=fromMm(2))
return sinkE + solid.utils.down(sinkH)(sink) + solid.utils.down(HOLE_LEN)(hole)
def mirrorX(linestring, origin):
return [(2 * origin - x, y) for x, y in linestring]
def makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, topSide):
bBox = findBoardBoundingBox(board)
centerpoint = rectCenter(bBox)
top = jigThickness - fromMm(0.15)
pcbBottom = jigThickness - pcbThickness
outerPolygon, holes = createOuterPolygon(board, jigFrameSize, outerBorder)
outerRing = outerPolygon.exterior.coords
if topSide:
outerRing = mirrorX(outerRing, centerpoint[0])
body = solid.linear_extrude(height=top, convexity=10)(solid.polygon(
outerRing))
innerRings = [x.exterior.coords for x in listGeometries(createOffsetPolygon(board, - innerBorder))]
if topSide:
innerRings = [mirrorX(innerRing, centerpoint[0]) for innerRing in innerRings]
innerCutout = solid.utils.down(jigThickness)(
solid.linear_extrude(height=3 * jigThickness, convexity=10)(solid.polygon(innerRings[0])))
for innerRing in innerRings[1:]:
innerCutout = innerCutout + solid.utils.down(jigThickness)(
solid.linear_extrude(height=3 * jigThickness, convexity=10)(solid.polygon(innerRing)))
registerRing = createOffsetPolygon(board, tolerance).exterior.coords
if topSide:
registerRing = mirrorX(registerRing, centerpoint[0])
registerCutout = solid.utils.up(jigThickness - pcbThickness)(
solid.linear_extrude(height=jigThickness, convexity=10)(solid.polygon(registerRing)))
register = body - innerCutout - registerCutout
for hole in holes:
register = register - solid.translate([hole[0], hole[1], top])(m2countersink())
return solid.scale(toMm(1))(
solid.translate([-centerpoint[0], -centerpoint[1], 0])(register))
def makeTopRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder=fromMm(3), innerBorder=fromMm(1),
tolerance=fromMm(0.05)):
"""
Create a SolidPython representation of the top register
"""
return makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, True)
def makeBottomRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder=fromMm(3), innerBorder=fromMm(1),
tolerance=fromMm(0.05)):
"""
Create a SolidPython representation of the top register
"""
return makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, False)
def renderScad(infile, outfile):
infile = os.path.abspath(infile)
outfile = os.path.abspath(outfile)
try:
subprocess.run(["openscad", "-o", outfile, infile],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as e:
message = f"Cannot render {outfile}, OpenSCAD error:\n"
message += (e.stdout.decode("utf-8") + "\n") if e.stdout is not None else ""
message += (e.stderr.decode("utf-8") + "\n") if e.stderr is not None else ""
raise RuntimeError(message)
except FileNotFoundError as e:
message = f"OpenSCAD is not available.\n"
message += f"Did you install it? Program `openscad` has to be in PATH"
raise RuntimeError(message)
def shapelyToSHAPE_POLY_SET(polygon):
p = pcbnew.SHAPE_POLY_SET()
p.AddOutline(linestringToKicad(polygon.exterior))
return p
def cutoutComponents(board, components):
topCutout = extractComponentPolygons(components, pcbnew.F_CrtYd)
for polygon in topCutout:
zone = pcbnew.PCB_SHAPE()
zone.SetShape(STROKE_T.S_POLYGON)
zone.SetPolyShape(shapelyToSHAPE_POLY_SET(polygon))
zone.SetLayer(Layer.F_Paste)
board.Add(zone)
bottomCutout = extractComponentPolygons(components, pcbnew.B_CrtYd)
for polygon in bottomCutout:
zone = pcbnew.PCB_SHAPE()
zone.SetShape(STROKE_T.S_POLYGON)
zone.SetPolyShape(shapelyToSHAPE_POLY_SET(polygon))
zone.SetLayer(Layer.B_Paste)
board.Add(zone)
def setStencilLayerVisibility(boardName):
prlPath = os.path.splitext(boardName)[0] + ".kicad_prl"
try:
with open(prlPath, encoding="utf-8") as f:
# We use ordered dict, so we preserve the ordering of the keys and
# thus, formatting
prl = json.load(f, object_pairs_hook=OrderedDict)
except FileNotFoundError:
# KiCAD didn't generate project local settings, let's create an empty one
prl = {
"board": {}
}
prl["board"]["visible_layers"] = "ffc000c_7ffffffe"
prl["board"]["visible_items"] = [
1,
2,
3,
4,
9,
10,
12,
13,
21,
22,
24,
25,
26,
27,
28,
29,
30,
34,
35
]
with open(prlPath, "w", encoding="utf-8") as f:
json.dump(prl, f, indent=2)
pass
from pathlib import Path
import os
def create(inputboard, outputdir, jigsize, jigthickness, pcbthickness,
registerborder, tolerance, ignore, cutout):
board = pcbnew.LoadBoard(inputboard)
refs = parseReferences(ignore)
removeComponents(board, refs)
Path(outputdir).mkdir(parents=True, exist_ok=True)
jigsize = (fromMm(jigsize[0]), fromMm(jigsize[1]))
addJigFrame(board, jigsize)
cutoutComponents(board, getComponents(board, parseReferences(cutout)))
stencilFile = os.path.join(outputdir, "stencil.kicad_pcb")
board.Save(stencilFile)
setStencilLayerVisibility(stencilFile)
plotPlan = [
# name, id, comment
("PasteBottom", pcbnew.B_Paste, "Paste Bottom"),
("PasteTop", pcbnew.F_Paste, "Paste top"),
]
# get a copy of exportSettingsJlcpcb dictionary and
# exclude the Edge.Cuts layer for creation of stencil gerber files
exportSettings = exportSettingsJlcpcb.copy()
exportSettings["ExcludeEdgeLayer"] = True
gerberDir = os.path.join(outputdir, "gerber")
gerberImpl(stencilFile, gerberDir, plotPlan, False, exportSettings)
shutil.make_archive(os.path.join(outputdir, "gerbers"), "zip", gerberDir)
jigthickness = fromMm(jigthickness)
pcbthickness = fromMm(pcbthickness)
outerBorder, innerBorder = fromMm(registerborder[0]), fromMm(registerborder[1])
tolerance = fromMm(tolerance)
topRegister = makeTopRegister(board, jigsize,jigthickness, pcbthickness,
outerBorder, innerBorder, tolerance)
bottomRegister = makeBottomRegister(board, jigsize,jigthickness, pcbthickness,
outerBorder, innerBorder, tolerance)
topRegisterFile = os.path.join(outputdir, "topRegister.scad")
solid.scad_render_to_file(topRegister, topRegisterFile)
renderScad(topRegisterFile, os.path.join(outputdir, "topRegister.stl"))
bottomRegisterFile = os.path.join(outputdir, "bottomRegister.scad")
solid.scad_render_to_file(bottomRegister, bottomRegisterFile)
renderScad(bottomRegisterFile, os.path.join(outputdir, "bottomRegister.stl"))
def printedStencilSubstrate(outlineDxf, thickness, frameHeight, frameWidth, frameClearance):
bodyOffset = solid.utils.up(0) if frameWidth + frameClearance == 0 else solid.offset(r=frameWidth + frameClearance)
body = solid.linear_extrude(height=thickness + frameHeight)(
bodyOffset(solid.import_dxf(outlineDxf)))
boardOffset = solid.utils.up(0) if frameClearance == 0 else solid.offset(r=frameClearance)
board = solid.utils.up(thickness)(
solid.linear_extrude(height=thickness + frameHeight)(
boardOffset(solid.import_dxf(outlineDxf))))
return body - board
def getComponents(board, references):
"""
Return a list of components based on designator
"""
return [f for f in board.GetFootprints() if f.GetReference() in references]
def collectFootprintEdges(footprint, layer):
"""
Return all edges on given layer in given footprint
"""
return [e for e in footprint.GraphicalItems() if e.GetLayer() == layer]
def extractComponentPolygons(footprints, srcLayer):
"""
Return a list of shapely polygons with holes for already placed components.
The source layer defines the geometry on which the cutout is computed.
Usually it a font or back courtyard
"""
polygons = []
for f in footprints:
edges = collectFootprintEdges(f, srcLayer)
for ring in extractRings(edges):
polygons.append(toShapely(ring, edges))
return polygons
def printedStencil(outlineDxf, holesDxf, extraHoles, thickness, frameHeight, frameWidth,
frameClearance, enlargeHoles, front):
zScale = -1 if front else 1
xRotate = 180 if front else 0
substrate = solid.scale([1, 1, zScale])(printedStencilSubstrate(outlineDxf,
thickness, frameHeight, frameWidth, frameClearance))
holesOffset = solid.utils.up(0) if enlargeHoles == 0 else solid.offset(delta=enlargeHoles)
holes = solid.linear_extrude(height=4*thickness, center=True)(
holesOffset(solid.import_dxf(holesDxf)))
substrate -= holes
for h in extraHoles:
substrate -= solid.scale([toMm(1), -toMm(1), 1])(
solid.linear_extrude(height=4*thickness, center=True)(
solid.polygon(h.exterior.coords)))
return solid.rotate(a=xRotate, v=[1, 0, 0])(substrate)
def createPrinted(inputboard, outputdir, pcbthickness, thickness, framewidth,
ignore, cutout, frameclearance, enlargeholes):
"""
Create a 3D printed self-registering stencil.
"""
board = pcbnew.LoadBoard(inputboard)
refs = parseReferences(ignore)
cutoutComponents = getComponents(board, parseReferences(cutout))
removeComponents(board, refs)
Path(outputdir).mkdir(parents=True, exist_ok=True)
# We create the stencil based on DXF export. Using it avoids the necessity
# to interpret KiCAD PAD shapes which constantly change with newer and newer
# versions.
height = min(pcbthickness, max(0.5, pcbthickness - 0.3))
bottomPaste, topPaste, outline = pasteDxfExport(board, outputdir)
# On Windows, OpenSCAD requires to use forward slashes instead of backslashes,
# hence, the replacement:
if os.name == "nt":
bottomPaste = bottomPaste.replace("\\", "/")
topPaste = topPaste.replace("\\", "/")
outline = outline.replace("\\", "/")
topCutout = extractComponentPolygons(cutoutComponents, pcbnew.F_CrtYd)
bottomCutout = extractComponentPolygons(cutoutComponents, pcbnew.B_CrtYd)
topStencil = printedStencil(outline, topPaste, topCutout, thickness, height,
framewidth, frameclearance, enlargeholes, True)
bottomStencil = printedStencil(outline, bottomPaste, bottomCutout, thickness,
height, framewidth, frameclearance, enlargeholes, False)
bottomStencilFile = os.path.join(outputdir, "bottomStencil.scad")
solid.scad_render_to_file(bottomStencil, bottomStencilFile,
file_header=f'$fa = 0.4; $fs = 0.4;', include_orig_code=True)
renderScad(bottomStencilFile, os.path.join(outputdir, "bottomStencil.stl"))
topStencilFile = os.path.join(outputdir, "topStencil.scad")
solid.scad_render_to_file(topStencil, topStencilFile,
file_header=f'$fa = 0.4; $fs = 0.4;', include_orig_code=True)
renderScad(topStencilFile, os.path.join(outputdir, "topStencil.stl"))
************************************************/
File diff suppressed because it is too large. Load diff
+386
View File
@@ -0,0 +1,386 @@
0
SECTION
2
HEADER
9
$ANGBASE
50
0.0
9
$ANGDIR
70
1
9
$MEASUREMENT
70
1
0
ENDSEC
0
SECTION
2
TABLES
0
TABLE
2
LTYPE
70
4
0
LTYPE
5
40F
2
CONTINUOUS
70
0
3
Solid line
72
65
73
0
40
0.0
0
LTYPE
5
410
2
DASHDOT
70
0
3
Dash Dot ____ _ ____ _
72
65
73
4
40
2.0
49
1.25
49
-0.25
49
0.25
49
-0.25
0
LTYPE
5
411
2
DASHED
70
0
3
Dashed __ __ __ __ __
72
65
73
2
40
0.75
49
0.5
49
-0.25
0
LTYPE
5
43B
2
DOTTED
70
0
3
Dotted . . . .
72
65
73
2
40
0.2
49
0.0
49
-0.2
0
ENDTAB
0
TABLE
2
STYLE
70
4
0
STYLE
2
KICAD
70
0
40
0
41
1
42
1
50
0
71
0
3
isocp.shx
0
STYLE
2
KICADB
70
0
40
0
41
1
42
1
50
0
71
0
3
isocp.shx
0
STYLE
2
KICADI
70
0
40
0
41
1
42
1
50
15
71
0
3
isocp.shx
0
STYLE
2
KICADBI
70
0
40
0
41
1
42
1
50
15
71
0
3
isocp.shx
0
ENDTAB
0
TABLE
2
LAYER
70
1
0
LAYER
2
BLACK
70
0
62
7
6
CONTINUOUS
0
ENDTAB
0
ENDSEC
0
SECTION
2
ENTITIES
0
LINE
8
BLACK
6
CONTINUOUS
10
219.2245509999999911
20
-37.0334679999999992
11
219.2500619999999856
21
-108.9006239999999934
0
LINE
8
BLACK
6
CONTINUOUS
10
219.2500619999999856
20
-108.9006239999999934
11
133.100087000000002
21
-108.875624000000002
0
LINE
8
BLACK
6
CONTINUOUS
10
133.100087000000002
20
-108.875624000000002
11
133.0750740000000008
21
-58.3999999999999986
0
LINE
8
BLACK
6
CONTINUOUS
10
133.0750740000000008
20
-58.3999999999999986
11
139.6749999999999829
21
-58.3999999999999986
0
LINE
8
BLACK
6
CONTINUOUS
10
139.6749999999999829
20
-58.3999999999999986
11
139.6749999999999829
21
-57.7999999999999972
0
LINE
8
BLACK
6
CONTINUOUS
10
139.6749999999999829
20
-57.7999999999999972
11
139.25
21
-57.7999999999999972
0
LINE
8
BLACK
6
CONTINUOUS
10
139.25
20
-57.7999999999999972
11
139.25
21
-49.75
0
LINE
8
BLACK
6
CONTINUOUS
10
139.25
20
-49.75
11
139.6999999999999886
21
-49.75
0
LINE
8
BLACK
6
CONTINUOUS
10
139.6999999999999886
20
-49.75
11
139.6999999999999886
21
-49.1499999999999986
0
LINE
8
BLACK
6
CONTINUOUS
10
139.6999999999999886
20
-49.1499999999999986
11
133.0749999999999886
21
-49.1499999999999986
0
LINE
8
BLACK
6
CONTINUOUS
10
133.0749999999999886
20
-49.1499999999999986
11
133.0749999999999886
21
-37.0084680000000006
0
LINE
8
BLACK
6
CONTINUOUS
10
133.0749999999999886
20
-37.0084680000000006
11
219.2245509999999911
21
-37.0334679999999992
0
ENDSEC
0
EOF
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+531
View File
@@ -0,0 +1,531 @@
// Generated by SolidPython <Unknown> on 2025-09-08 06:43:20
$fa = 0.4; $fs = 0.4;
rotate(a = 180, v = [1, 0, 0]) {
difference() {
scale(v = [1, 1, -1]) {
difference() {
linear_extrude(height = 1.4500000000) {
offset(r = 1.0000000000) {
import(file = "c64psu-stencil_3d_edge.dxf", origin = [0, 0]);
}
}
translate(v = [0, 0, 0.1500000000]) {
linear_extrude(height = 1.4500000000) {
translate(v = [0, 0, 0]) {
import(file = "c64psu-stencil_3d_edge.dxf", origin = [0, 0]);
}
}
}
}
}
linear_extrude(center = true, height = 0.6000000000) {
translate(v = [0, 0, 0]) {
import(file = "c64psu-stencil_3d_top.dxf", origin = [0, 0]);
}
}
}
}
/***********************************************
********* SolidPython code: **********
************************************************
from pcbnewTransition import pcbnew
import numpy as np
import json
from collections import OrderedDict
from kikit.common import *
from kikit.defs import *
from kikit.substrate import Substrate, extractRings, toShapely, linestringToKicad
from kikit.export import gerberImpl, pasteDxfExport
from kikit.export import exportSettingsJlcpcb
# Allow importing solid from this dir
import os
import sys
cur_path = os.path.abspath(os.path.dirname(__file__))
if cur_path not in sys.path:
sys.path.insert(0, cur_path)
import solid
import solid.utils
import subprocess
import shutil
from kikit.common import removeComponents, parseReferences
from shapely.geometry import Point
OUTER_BORDER = fromMm(7.5)
INNER_BORDER = fromMm(5)
MOUNTING_HOLES_COUNT = 3
MOUNTING_HOLE_R = fromMm(1)
HOLE_SPACING = fromMm(20)
def addBottomCounterpart(board, item):
item = item.Duplicate()
item.SetLayer(Layer.B_Paste)
board.Add(item)
def addRoundedCorner(board, center, start, end, thickness):
corner = pcbnew.PCB_SHAPE()
corner.SetShape(STROKE_T.S_ARC)
corner.SetCenter(toKiCADPoint((center[0], center[1])))
corner.SetStart(toKiCADPoint((start[0], start[1])))
if np.cross(start - center, end - center) > 0:
corner.SetArcAngleAndEnd(fromDegrees(90), True)
else:
corner.SetArcAngleAndEnd(fromDegrees(-90), True)
corner.SetWidth(thickness)
corner.SetLayer(Layer.F_Paste)
board.Add(corner)
addBottomCounterpart(board, corner)
def addLine(board, start, end, thickness):
line = pcbnew.PCB_SHAPE()
line.SetShape(STROKE_T.S_SEGMENT)
line.SetStart(toKiCADPoint((start[0], start[1])))
line.SetEnd(toKiCADPoint((end[0], end[1])))
line.SetWidth(thickness)
line.SetLayer(Layer.F_Paste)
board.Add(line)
addBottomCounterpart(board, line)
def addBite(board, origin, direction, normal, thickness):
"""
Adds a bite to the stencil, direction points to the bridge, normal points
inside the stencil
"""
direction = normalize(direction) * thickness
normal = normalize(normal) * thickness
center = toKiCADPoint((origin[0], origin[1])) + toKiCADPoint((normal[0], normal[1]))
start = origin
end = center + toKiCADPoint((direction[0], direction[1]))
# addLine(board, end, end + normal / 2, thickness)
addRoundedCorner(board, center, start, end, thickness)
def numberOfCuts(length, bridgeWidth, bridgeSpacing):
"""
Return number of bridges which fit inside the length and cut length
"""
count = round(np.floor((length + bridgeWidth) / (bridgeWidth + bridgeSpacing)))
cutLength = round((length - (count - 1) * bridgeWidth) / count)
return count, cutLength
def addFrame(board, rect, bridgeWidth, bridgeSpacing, clearance):
"""
Add rectangular frame to the board
"""
R=fromMm(1)
corners = [
(tl(rect), toKiCADPoint((R, 0)), toKiCADPoint((0, R))), # TL
(tr(rect), toKiCADPoint((0, R)), toKiCADPoint((-R, 0))), # TR
(br(rect), toKiCADPoint((-R, 0)), toKiCADPoint((0, -R))), # BR
(bl(rect), toKiCADPoint((0, -R)), toKiCADPoint((R, 0))) # BL
]
for c, sOffset, eOffset in corners:
addRoundedCorner(board, c + sOffset + eOffset, c + sOffset, c + eOffset, clearance)
count, cutLength = numberOfCuts(rect.GetWidth() - 2 * R, bridgeWidth, bridgeSpacing)
for i in range(count):
start = round(rect.GetX() + R + i * bridgeWidth + i * cutLength)
end = start + cutLength
y1, y2 = rect.GetY(), rect.GetY() + rect.GetHeight()
addLine(board, toKiCADPoint((start, y1)), toKiCADPoint((end, y1)), clearance)
if i != 0:
addBite(board, toKiCADPoint((start, y1)), toKiCADPoint((-1, 0)), toKiCADPoint((0, 1)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((end, y1)), toKiCADPoint((1, 0)), toKiCADPoint((0, 1)), clearance)
addLine(board, toKiCADPoint((start, y2)), toKiCADPoint((end, y2)), clearance)
if i != 0:
addBite(board, toKiCADPoint((start, y2)), toKiCADPoint((-1, 0)), toKiCADPoint((0, -1)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((end, y2)), toKiCADPoint((1, 0)), toKiCADPoint((0, -1)), clearance)
count, cutLength = numberOfCuts(rect.GetHeight() - 2 * R, bridgeWidth, bridgeSpacing)
for i in range(count):
start = rect.GetY() + R + i * bridgeWidth + i * cutLength
end = start + cutLength
x1, x2 = rect.GetX(), rect.GetX() + rect.GetWidth()
addLine(board, toKiCADPoint((x1, start)), toKiCADPoint((x1, end)), clearance)
if i != 0:
addBite(board, toKiCADPoint((x1, start)), toKiCADPoint((0, -1)), toKiCADPoint((1, 0)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((x1, end)), toKiCADPoint((0, 1)), toKiCADPoint((1, 0)), clearance)
addLine(board, toKiCADPoint((x2, start)), toKiCADPoint((x2, end)), clearance)
if i != 0:
addBite(board, toKiCADPoint((x2, start)), toKiCADPoint((0, -1)), toKiCADPoint((-1, 0)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((x2, end)), toKiCADPoint((0, 1)), toKiCADPoint((-1, 0)), clearance)
def addHole(board, position, radius):
circle = pcbnew.PCB_SHAPE()
circle.SetShape(STROKE_T.S_CIRCLE)
circle.SetCenter(toKiCADPoint((position[0], position[1])))
# Set 3'oclock point of the circle to set radius
circle.SetEnd(toKiCADPoint((position[0], position[1])) + toKiCADPoint((radius/2, 0)))
circle.SetWidth(radius)
circle.SetLayer(Layer.F_Paste)
board.Add(circle)
addBottomCounterpart(board, circle)
def addJigFrame(board, jigFrameSize, bridgeWidth=fromMm(2),
bridgeSpacing=fromMm(10), clearance=fromMm(0.5)):
"""
Given a Pcbnew board finds the board outline and creates a stencil for
KiKit's stencil jig.
Mainly, adds mounting holes and mouse bites to define the panel outline.
jigFrameSize is a tuple (width, height).
"""
bBox = findBoardBoundingBox(board)
frameSize = rectByCenter(rectCenter(bBox),
jigFrameSize[0] + 2 * (OUTER_BORDER + INNER_BORDER),
jigFrameSize[1] + 2 * (OUTER_BORDER + INNER_BORDER))
cutSize = rectByCenter(rectCenter(bBox),
jigFrameSize[0] + 2 * (OUTER_BORDER + INNER_BORDER) - fromMm(1),
jigFrameSize[1] + 2 * (OUTER_BORDER + INNER_BORDER) - fromMm(1))
addFrame(board, cutSize, bridgeWidth, bridgeSpacing, clearance)
for i in range(MOUNTING_HOLES_COUNT):
x = frameSize.GetX() + OUTER_BORDER / 2 + (i + 1) * (frameSize.GetWidth() - OUTER_BORDER) / (MOUNTING_HOLES_COUNT + 1)
addHole(board, toKiCADPoint((x, OUTER_BORDER / 2 + frameSize.GetY())), MOUNTING_HOLE_R)
addHole(board, toKiCADPoint((x, - OUTER_BORDER / 2 +frameSize.GetY() + frameSize.GetHeight())), MOUNTING_HOLE_R)
for i in range(MOUNTING_HOLES_COUNT):
y = frameSize.GetY() + OUTER_BORDER / 2 + (i + 1) * (frameSize.GetHeight() - OUTER_BORDER) / (MOUNTING_HOLES_COUNT + 1)
addHole(board, toKiCADPoint((OUTER_BORDER / 2 + frameSize.GetX(), y)), MOUNTING_HOLE_R)
addHole(board, toKiCADPoint((- OUTER_BORDER / 2 +frameSize.GetX() + frameSize.GetWidth(), y)), MOUNTING_HOLE_R)
PIN_TOLERANCE = fromMm(0.05)
addHole(board, tl(frameSize) + toKiCADPoint((OUTER_BORDER / 2, OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, tr(frameSize) + toKiCADPoint((-OUTER_BORDER / 2, OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, br(frameSize) + toKiCADPoint((-OUTER_BORDER / 2, -OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, bl(frameSize) + toKiCADPoint((OUTER_BORDER / 2, -OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
def jigMountingHoles(jigFrameSize, origin=toKiCADPoint((0, 0))):
""" Get list of all mounting holes in a jig of given size """
w, h = jigFrameSize
holes = [
toKiCADPoint((0, (w + INNER_BORDER) / 2)),
toKiCADPoint((0, -(w + INNER_BORDER) / 2)),
toKiCADPoint(((h + INNER_BORDER) / 2, 0)),
toKiCADPoint((-(h + INNER_BORDER) / 2, 0)),
]
return [x + origin for x in holes]
def createOuterPolygon(board, jigFrameSize, outerBorder):
bBox = findBoardBoundingBox(board)
centerpoint = rectCenter(bBox)
holes = jigMountingHoles(jigFrameSize, centerpoint)
outerSubstrate = Substrate(collectEdges(board, Layer.Edge_Cuts))
outerSubstrate.substrates = outerSubstrate.substrates.buffer(outerBorder)
tabs = []
for hole in holes:
tab, _ = outerSubstrate.tab(hole, centerpoint - hole, INNER_BORDER, maxHeight=fromMm(1000))
tabs.append(tab)
outerSubstrate.union(tabs)
outerSubstrate.union([Point(x).buffer(INNER_BORDER / 2) for x in holes])
outerSubstrate.millFillets(fromMm(3))
return outerSubstrate.exterior(), holes
def createOffsetPolygon(board, offset):
outerSubstrate = Substrate(collectEdges(board, Layer.Edge_Cuts))
outerSubstrate.substrates = outerSubstrate.substrates.buffer(offset)
return outerSubstrate.exterior()
def m2countersink():
HEAD_DIA = fromMm(4.5)
HOLE_LEN = fromMm(10)
SINK_EXTRA = fromMm(0.3)
sinkH = np.sqrt(HEAD_DIA**2 / 4)
sink = solid.cylinder(d1=0, d2=HEAD_DIA, h=sinkH)
sinkE = solid.cylinder(d=HEAD_DIA, h=SINK_EXTRA)
hole = solid.cylinder(h=HOLE_LEN, d=fromMm(2))
return sinkE + solid.utils.down(sinkH)(sink) + solid.utils.down(HOLE_LEN)(hole)
def mirrorX(linestring, origin):
return [(2 * origin - x, y) for x, y in linestring]
def makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, topSide):
bBox = findBoardBoundingBox(board)
centerpoint = rectCenter(bBox)
top = jigThickness - fromMm(0.15)
pcbBottom = jigThickness - pcbThickness
outerPolygon, holes = createOuterPolygon(board, jigFrameSize, outerBorder)
outerRing = outerPolygon.exterior.coords
if topSide:
outerRing = mirrorX(outerRing, centerpoint[0])
body = solid.linear_extrude(height=top, convexity=10)(solid.polygon(
outerRing))
innerRings = [x.exterior.coords for x in listGeometries(createOffsetPolygon(board, - innerBorder))]
if topSide:
innerRings = [mirrorX(innerRing, centerpoint[0]) for innerRing in innerRings]
innerCutout = solid.utils.down(jigThickness)(
solid.linear_extrude(height=3 * jigThickness, convexity=10)(solid.polygon(innerRings[0])))
for innerRing in innerRings[1:]:
innerCutout = innerCutout + solid.utils.down(jigThickness)(
solid.linear_extrude(height=3 * jigThickness, convexity=10)(solid.polygon(innerRing)))
registerRing = createOffsetPolygon(board, tolerance).exterior.coords
if topSide:
registerRing = mirrorX(registerRing, centerpoint[0])
registerCutout = solid.utils.up(jigThickness - pcbThickness)(
solid.linear_extrude(height=jigThickness, convexity=10)(solid.polygon(registerRing)))
register = body - innerCutout - registerCutout
for hole in holes:
register = register - solid.translate([hole[0], hole[1], top])(m2countersink())
return solid.scale(toMm(1))(
solid.translate([-centerpoint[0], -centerpoint[1], 0])(register))
def makeTopRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder=fromMm(3), innerBorder=fromMm(1),
tolerance=fromMm(0.05)):
"""
Create a SolidPython representation of the top register
"""
return makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, True)
def makeBottomRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder=fromMm(3), innerBorder=fromMm(1),
tolerance=fromMm(0.05)):
"""
Create a SolidPython representation of the top register
"""
return makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, False)
def renderScad(infile, outfile):
infile = os.path.abspath(infile)
outfile = os.path.abspath(outfile)
try:
subprocess.run(["openscad", "-o", outfile, infile],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as e:
message = f"Cannot render {outfile}, OpenSCAD error:\n"
message += (e.stdout.decode("utf-8") + "\n") if e.stdout is not None else ""
message += (e.stderr.decode("utf-8") + "\n") if e.stderr is not None else ""
raise RuntimeError(message)
except FileNotFoundError as e:
message = f"OpenSCAD is not available.\n"
message += f"Did you install it? Program `openscad` has to be in PATH"
raise RuntimeError(message)
def shapelyToSHAPE_POLY_SET(polygon):
p = pcbnew.SHAPE_POLY_SET()
p.AddOutline(linestringToKicad(polygon.exterior))
return p
def cutoutComponents(board, components):
topCutout = extractComponentPolygons(components, pcbnew.F_CrtYd)
for polygon in topCutout:
zone = pcbnew.PCB_SHAPE()
zone.SetShape(STROKE_T.S_POLYGON)
zone.SetPolyShape(shapelyToSHAPE_POLY_SET(polygon))
zone.SetLayer(Layer.F_Paste)
board.Add(zone)
bottomCutout = extractComponentPolygons(components, pcbnew.B_CrtYd)
for polygon in bottomCutout:
zone = pcbnew.PCB_SHAPE()
zone.SetShape(STROKE_T.S_POLYGON)
zone.SetPolyShape(shapelyToSHAPE_POLY_SET(polygon))
zone.SetLayer(Layer.B_Paste)
board.Add(zone)
def setStencilLayerVisibility(boardName):
prlPath = os.path.splitext(boardName)[0] + ".kicad_prl"
try:
with open(prlPath, encoding="utf-8") as f:
# We use ordered dict, so we preserve the ordering of the keys and
# thus, formatting
prl = json.load(f, object_pairs_hook=OrderedDict)
except FileNotFoundError:
# KiCAD didn't generate project local settings, let's create an empty one
prl = {
"board": {}
}
prl["board"]["visible_layers"] = "ffc000c_7ffffffe"
prl["board"]["visible_items"] = [
1,
2,
3,
4,
9,
10,
12,
13,
21,
22,
24,
25,
26,
27,
28,
29,
30,
34,
35
]
with open(prlPath, "w", encoding="utf-8") as f:
json.dump(prl, f, indent=2)
pass
from pathlib import Path
import os
def create(inputboard, outputdir, jigsize, jigthickness, pcbthickness,
registerborder, tolerance, ignore, cutout):
board = pcbnew.LoadBoard(inputboard)
refs = parseReferences(ignore)
removeComponents(board, refs)
Path(outputdir).mkdir(parents=True, exist_ok=True)
jigsize = (fromMm(jigsize[0]), fromMm(jigsize[1]))
addJigFrame(board, jigsize)
cutoutComponents(board, getComponents(board, parseReferences(cutout)))
stencilFile = os.path.join(outputdir, "stencil.kicad_pcb")
board.Save(stencilFile)
setStencilLayerVisibility(stencilFile)
plotPlan = [
# name, id, comment
("PasteBottom", pcbnew.B_Paste, "Paste Bottom"),
("PasteTop", pcbnew.F_Paste, "Paste top"),
]
# get a copy of exportSettingsJlcpcb dictionary and
# exclude the Edge.Cuts layer for creation of stencil gerber files
exportSettings = exportSettingsJlcpcb.copy()
exportSettings["ExcludeEdgeLayer"] = True
gerberDir = os.path.join(outputdir, "gerber")
gerberImpl(stencilFile, gerberDir, plotPlan, False, exportSettings)
shutil.make_archive(os.path.join(outputdir, "gerbers"), "zip", gerberDir)
jigthickness = fromMm(jigthickness)
pcbthickness = fromMm(pcbthickness)
outerBorder, innerBorder = fromMm(registerborder[0]), fromMm(registerborder[1])
tolerance = fromMm(tolerance)
topRegister = makeTopRegister(board, jigsize,jigthickness, pcbthickness,
outerBorder, innerBorder, tolerance)
bottomRegister = makeBottomRegister(board, jigsize,jigthickness, pcbthickness,
outerBorder, innerBorder, tolerance)
topRegisterFile = os.path.join(outputdir, "topRegister.scad")
solid.scad_render_to_file(topRegister, topRegisterFile)
renderScad(topRegisterFile, os.path.join(outputdir, "topRegister.stl"))
bottomRegisterFile = os.path.join(outputdir, "bottomRegister.scad")
solid.scad_render_to_file(bottomRegister, bottomRegisterFile)
renderScad(bottomRegisterFile, os.path.join(outputdir, "bottomRegister.stl"))
def printedStencilSubstrate(outlineDxf, thickness, frameHeight, frameWidth, frameClearance):
bodyOffset = solid.utils.up(0) if frameWidth + frameClearance == 0 else solid.offset(r=frameWidth + frameClearance)
body = solid.linear_extrude(height=thickness + frameHeight)(
bodyOffset(solid.import_dxf(outlineDxf)))
boardOffset = solid.utils.up(0) if frameClearance == 0 else solid.offset(r=frameClearance)
board = solid.utils.up(thickness)(
solid.linear_extrude(height=thickness + frameHeight)(
boardOffset(solid.import_dxf(outlineDxf))))
return body - board
def getComponents(board, references):
"""
Return a list of components based on designator
"""
return [f for f in board.GetFootprints() if f.GetReference() in references]
def collectFootprintEdges(footprint, layer):
"""
Return all edges on given layer in given footprint
"""
return [e for e in footprint.GraphicalItems() if e.GetLayer() == layer]
def extractComponentPolygons(footprints, srcLayer):
"""
Return a list of shapely polygons with holes for already placed components.
The source layer defines the geometry on which the cutout is computed.
Usually it a font or back courtyard
"""
polygons = []
for f in footprints:
edges = collectFootprintEdges(f, srcLayer)
for ring in extractRings(edges):
polygons.append(toShapely(ring, edges))
return polygons
def printedStencil(outlineDxf, holesDxf, extraHoles, thickness, frameHeight, frameWidth,
frameClearance, enlargeHoles, front):
zScale = -1 if front else 1
xRotate = 180 if front else 0
substrate = solid.scale([1, 1, zScale])(printedStencilSubstrate(outlineDxf,
thickness, frameHeight, frameWidth, frameClearance))
holesOffset = solid.utils.up(0) if enlargeHoles == 0 else solid.offset(delta=enlargeHoles)
holes = solid.linear_extrude(height=4*thickness, center=True)(
holesOffset(solid.import_dxf(holesDxf)))
substrate -= holes
for h in extraHoles:
substrate -= solid.scale([toMm(1), -toMm(1), 1])(
solid.linear_extrude(height=4*thickness, center=True)(
solid.polygon(h.exterior.coords)))
return solid.rotate(a=xRotate, v=[1, 0, 0])(substrate)
def createPrinted(inputboard, outputdir, pcbthickness, thickness, framewidth,
ignore, cutout, frameclearance, enlargeholes):
"""
Create a 3D printed self-registering stencil.
"""
board = pcbnew.LoadBoard(inputboard)
refs = parseReferences(ignore)
cutoutComponents = getComponents(board, parseReferences(cutout))
removeComponents(board, refs)
Path(outputdir).mkdir(parents=True, exist_ok=True)
# We create the stencil based on DXF export. Using it avoids the necessity
# to interpret KiCAD PAD shapes which constantly change with newer and newer
# versions.
height = min(pcbthickness, max(0.5, pcbthickness - 0.3))
bottomPaste, topPaste, outline = pasteDxfExport(board, outputdir)
# On Windows, OpenSCAD requires to use forward slashes instead of backslashes,
# hence, the replacement:
if os.name == "nt":
bottomPaste = bottomPaste.replace("\\", "/")
topPaste = topPaste.replace("\\", "/")
outline = outline.replace("\\", "/")
topCutout = extractComponentPolygons(cutoutComponents, pcbnew.F_CrtYd)
bottomCutout = extractComponentPolygons(cutoutComponents, pcbnew.B_CrtYd)
topStencil = printedStencil(outline, topPaste, topCutout, thickness, height,
framewidth, frameclearance, enlargeholes, True)
bottomStencil = printedStencil(outline, bottomPaste, bottomCutout, thickness,
height, framewidth, frameclearance, enlargeholes, False)
bottomStencilFile = os.path.join(outputdir, "bottomStencil.scad")
solid.scad_render_to_file(bottomStencil, bottomStencilFile,
file_header=f'$fa = 0.4; $fs = 0.4;', include_orig_code=True)
renderScad(bottomStencilFile, os.path.join(outputdir, "bottomStencil.stl"))
topStencilFile = os.path.join(outputdir, "topStencil.scad")
solid.scad_render_to_file(topStencil, topStencilFile,
file_header=f'$fa = 0.4; $fs = 0.4;', include_orig_code=True)
renderScad(topStencilFile, os.path.join(outputdir, "topStencil.stl"))
************************************************/
File diff suppressed because it is too large. Load diff
+125
View File
@@ -0,0 +1,125 @@
{
"Header": {
"GenerationSoftware": {
"Vendor": "KiCad",
"Application": "Pcbnew",
"Version": "9.0.1+1"
},
"CreationDate": "2025-09-08T06:43:27+00:00"
},
"GeneralSpecs": {
"ProjectId": {
"Name": "stencil",
"GUID": "7374656e-6369-46c2-9e6b-696361645f70",
"Revision": "rev?"
},
"Size": {
"X": 86.2251,
"Y": 71.9422
},
"LayerNumber": 4,
"BoardThickness": 1.6,
"Finish": "None"
},
"DesignRules": [
{
"Layers": "Outer",
"PadToPad": 0.2,
"PadToTrack": 0.2,
"TrackToTrack": 0.2,
"MinLineWidth": 0.17,
"TrackToRegion": 0.2,
"RegionToRegion": 0.2
},
{
"Layers": "Inner",
"PadToPad": 0.2,
"PadToTrack": 0.2,
"TrackToTrack": 0.2,
"MinLineWidth": 0.21,
"TrackToRegion": 0.4,
"RegionToRegion": 0.4
}
],
"FilesAttributes": [
{
"Path": "c64psu-stencil_for_jig_bottom.gbp",
"FileFunction": "SolderPaste,Bot",
"FilePolarity": "Positive"
},
{
"Path": "c64psu-stencil_for_jig_top.gtp",
"FileFunction": "SolderPaste,Top",
"FilePolarity": "Positive"
}
],
"MaterialStackup": [
{
"Type": "Legend",
"Name": "Top Silk Screen"
},
{
"Type": "SolderPaste",
"Name": "Top Solder Paste"
},
{
"Type": "SolderMask",
"Thickness": 0.01,
"Name": "Top Solder Mask"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "F.Cu"
},
{
"Type": "Dielectric",
"Thickness": 0.1,
"Material": "FR4",
"Name": "F.Cu/In1.Cu",
"Notes": "Type: dielectric layer 1 (from F.Cu to In1.Cu)"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "In1.Cu"
},
{
"Type": "Dielectric",
"Thickness": 1.24,
"Material": "FR4",
"Name": "In1.Cu/In2.Cu",
"Notes": "Type: dielectric layer 2 (from In1.Cu to In2.Cu)"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "In2.Cu"
},
{
"Type": "Dielectric",
"Thickness": 0.1,
"Material": "FR4",
"Name": "In2.Cu/B.Cu",
"Notes": "Type: dielectric layer 3 (from In2.Cu to B.Cu)"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "B.Cu"
},
{
"Type": "SolderMask",
"Thickness": 0.01,
"Name": "Bottom Solder Mask"
},
{
"Type": "SolderPaste",
"Name": "Bottom Solder Paste"
},
{
"Type": "Legend",
"Name": "Bottom Silk Screen"
}
]
}
+776
View File
@@ -0,0 +1,776 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,9.0.1+1*
G04 #@! TF.CreationDate,2025-09-08T06:43:27+00:00*
G04 #@! TF.ProjectId,stencil,7374656e-6369-46c2-9e6b-696361645f70,rev?*
G04 #@! TF.SameCoordinates,Original*
G04 #@! TF.FileFunction,Paste,Bot*
G04 #@! TF.FilePolarity,Positive*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 9.0.1+1) date 2025-09-08 06:43:27*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 Aperture macros list*
%AMRoundRect*
0 Rectangle with rounded corners*
0 $1 Rounding radius*
0 $2 $3 $4 $5 $6 $7 $8 $9 X,Y pos of 4 corners*
0 Add a 4 corners polygon primitive as box body*
4,1,4,$2,$3,$4,$5,$6,$7,$8,$9,$2,$3,0*
0 Add four circle primitives for the rounded corners*
1,1,$1+$1,$2,$3*
1,1,$1+$1,$4,$5*
1,1,$1+$1,$6,$7*
1,1,$1+$1,$8,$9*
0 Add four rect primitives between the rounded corners*
20,1,$1+$1,$2,$3,$4,$5,0*
20,1,$1+$1,$4,$5,$6,$7,0*
20,1,$1+$1,$6,$7,$8,$9,0*
20,1,$1+$1,$8,$9,$2,$3,0*%
G04 Aperture macros list end*
%ADD10C,0.500000*%
%ADD11C,1.000000*%
%ADD12C,1.050000*%
%ADD13RoundRect,0.250000X0.650000X-1.000000X0.650000X1.000000X-0.650000X1.000000X-0.650000X-1.000000X0*%
%ADD14RoundRect,0.200000X-0.275000X0.200000X-0.275000X-0.200000X0.275000X-0.200000X0.275000X0.200000X0*%
%ADD15RoundRect,0.150000X0.512500X0.150000X-0.512500X0.150000X-0.512500X-0.150000X0.512500X-0.150000X0*%
%ADD16RoundRect,0.250000X-0.650000X0.325000X-0.650000X-0.325000X0.650000X-0.325000X0.650000X0.325000X0*%
%ADD17RoundRect,0.200000X0.200000X0.275000X-0.200000X0.275000X-0.200000X-0.275000X0.200000X-0.275000X0*%
%ADD18RoundRect,0.200000X0.275000X-0.200000X0.275000X0.200000X-0.275000X0.200000X-0.275000X-0.200000X0*%
%ADD19RoundRect,0.200000X-0.053033X0.335876X-0.335876X0.053033X0.053033X-0.335876X0.335876X-0.053033X0*%
%ADD20RoundRect,0.200000X-0.200000X-0.275000X0.200000X-0.275000X0.200000X0.275000X-0.200000X0.275000X0*%
%ADD21RoundRect,0.140000X-0.170000X0.140000X-0.170000X-0.140000X0.170000X-0.140000X0.170000X0.140000X0*%
%ADD22RoundRect,0.150000X-0.587500X-0.150000X0.587500X-0.150000X0.587500X0.150000X-0.587500X0.150000X0*%
%ADD23RoundRect,0.140000X0.170000X-0.140000X0.170000X0.140000X-0.170000X0.140000X-0.170000X-0.140000X0*%
%ADD24RoundRect,0.200000X-0.335876X-0.053033X-0.053033X-0.335876X0.335876X0.053033X0.053033X0.335876X0*%
G04 APERTURE END LIST*
D10*
X114662531Y-84854546D02*
G75*
G02*
X114162454Y-84354546I-31J500046D01*
G01*
X114662531Y-35254546D02*
G75*
G02*
X114162454Y-34754546I-31J500046D01*
G01*
X238162531Y-24354546D02*
X238162531Y-34754546D01*
X187562531Y-10954546D02*
G75*
G02*
X188062454Y-11454546I-31J-499954D01*
G01*
D11*
X206037531Y-131704546D02*
G75*
G02*
X205037531Y-131704546I-500000J0D01*
G01*
X205037531Y-131704546D02*
G75*
G02*
X206037531Y-131704546I500000J0D01*
G01*
D10*
X237662531Y-85854546D02*
G75*
G02*
X238162454Y-86354546I-31J-499954D01*
G01*
X164762531Y-134954546D02*
G75*
G02*
X164262454Y-134454546I-31J500046D01*
G01*
X237662531Y-23854546D02*
G75*
G02*
X238162454Y-24354546I-31J-499954D01*
G01*
X114162531Y-73954546D02*
G75*
G02*
X114662531Y-73454531I499969J46D01*
G01*
X114162531Y-98754546D02*
X114162531Y-109154546D01*
X115162531Y-134954546D02*
X125562531Y-134954546D01*
X237662531Y-73454546D02*
G75*
G02*
X238162454Y-73954546I-31J-499954D01*
G01*
X201962531Y-10954546D02*
X212362531Y-10954546D01*
X238162531Y-61554546D02*
X238162531Y-71954546D01*
X138462531Y-134454546D02*
G75*
G02*
X137962531Y-134954531I-500031J46D01*
G01*
D11*
X176662531Y-131704546D02*
G75*
G02*
X175662531Y-131704546I-500000J0D01*
G01*
X175662531Y-131704546D02*
G75*
G02*
X176662531Y-131704546I500000J0D01*
G01*
D10*
X115162531Y-10954546D02*
X125562531Y-10954546D01*
X127562531Y-134954546D02*
G75*
G02*
X127062454Y-134454546I-31J500046D01*
G01*
X237162531Y-10954546D02*
G75*
G02*
X238162454Y-11954546I-31J-999954D01*
G01*
X238162531Y-123554546D02*
X238162531Y-133954546D01*
X114162531Y-11954546D02*
X114162531Y-22354546D01*
X139962531Y-134954546D02*
G75*
G02*
X139462454Y-134454546I-31J500046D01*
G01*
X139962531Y-10954546D02*
X150362531Y-10954546D01*
X201962531Y-134954546D02*
X212362531Y-134954546D01*
X175162531Y-10954546D02*
G75*
G02*
X175662454Y-11454546I-31J-499954D01*
G01*
X163262531Y-134454546D02*
G75*
G02*
X162762531Y-134954531I-500031J46D01*
G01*
X114162531Y-123554546D02*
G75*
G02*
X114662531Y-123054531I499969J46D01*
G01*
X114162531Y-86354546D02*
G75*
G02*
X114662531Y-85854531I499969J46D01*
G01*
X237662531Y-98254546D02*
G75*
G02*
X238162454Y-98754546I-31J-499954D01*
G01*
X238162531Y-36754546D02*
X238162531Y-47154546D01*
X238162531Y-86354546D02*
X238162531Y-96754546D01*
D11*
X117912531Y-72954546D02*
G75*
G02*
X116912531Y-72954546I-500000J0D01*
G01*
X116912531Y-72954546D02*
G75*
G02*
X117912531Y-72954546I500000J0D01*
G01*
D10*
X238162531Y-34754546D02*
G75*
G02*
X237662531Y-35254531I-500031J46D01*
G01*
D11*
X235412531Y-72954546D02*
G75*
G02*
X234412531Y-72954546I-500000J0D01*
G01*
X234412531Y-72954546D02*
G75*
G02*
X235412531Y-72954546I500000J0D01*
G01*
D10*
X126062531Y-134454546D02*
G75*
G02*
X125562531Y-134954531I-500031J46D01*
G01*
X150362531Y-10954546D02*
G75*
G02*
X150862454Y-11454546I-31J-499954D01*
G01*
D11*
X147287531Y-14204546D02*
G75*
G02*
X146287531Y-14204546I-500000J0D01*
G01*
X146287531Y-14204546D02*
G75*
G02*
X147287531Y-14204546I500000J0D01*
G01*
X117912531Y-43579546D02*
G75*
G02*
X116912531Y-43579546I-500000J0D01*
G01*
X116912531Y-43579546D02*
G75*
G02*
X117912531Y-43579546I500000J0D01*
G01*
D10*
X199962531Y-10954546D02*
G75*
G02*
X200462454Y-11454546I-31J-499954D01*
G01*
X238162531Y-11954546D02*
X238162531Y-22354546D01*
X238162531Y-84354546D02*
G75*
G02*
X237662531Y-84854531I-500031J46D01*
G01*
X164762531Y-134954546D02*
X175162531Y-134954546D01*
X152362531Y-10954546D02*
X162762531Y-10954546D01*
X189562531Y-134954546D02*
G75*
G02*
X189062454Y-134454546I-31J500046D01*
G01*
X114162531Y-73954546D02*
X114162531Y-84354546D01*
X139962531Y-134954546D02*
X150362531Y-134954546D01*
D11*
X176662531Y-14204546D02*
G75*
G02*
X175662531Y-14204546I-500000J0D01*
G01*
X175662531Y-14204546D02*
G75*
G02*
X176662531Y-14204546I500000J0D01*
G01*
D10*
X225262531Y-134454546D02*
G75*
G02*
X224762531Y-134954531I-500031J46D01*
G01*
X137962531Y-10954546D02*
G75*
G02*
X138462454Y-11454546I-31J-499954D01*
G01*
X237662531Y-123054546D02*
G75*
G02*
X238162454Y-123554546I-31J-499954D01*
G01*
X114162531Y-111154546D02*
X114162531Y-121554546D01*
D12*
X117937531Y-14204546D02*
G75*
G02*
X116887531Y-14204546I-525000J0D01*
G01*
X116887531Y-14204546D02*
G75*
G02*
X117937531Y-14204546I525000J0D01*
G01*
D10*
X114162531Y-24354546D02*
X114162531Y-34754546D01*
X238162531Y-111154546D02*
X238162531Y-121554546D01*
X237662531Y-36254546D02*
G75*
G02*
X238162454Y-36754546I-31J-499954D01*
G01*
X114662531Y-72454546D02*
G75*
G02*
X114162454Y-71954546I-31J500046D01*
G01*
X177162531Y-10954546D02*
X187562531Y-10954546D01*
D12*
X117937531Y-131704546D02*
G75*
G02*
X116887531Y-131704546I-525000J0D01*
G01*
X116887531Y-131704546D02*
G75*
G02*
X117937531Y-131704546I525000J0D01*
G01*
D10*
X201962531Y-134954546D02*
G75*
G02*
X201462454Y-134454546I-31J500046D01*
G01*
X238162531Y-71954546D02*
G75*
G02*
X237662531Y-72454531I-500031J46D01*
G01*
X226762531Y-10954546D02*
X237162531Y-10954546D01*
X201462531Y-11454546D02*
G75*
G02*
X201962531Y-10954531I499969J46D01*
G01*
X212862531Y-134454546D02*
G75*
G02*
X212362531Y-134954531I-500031J46D01*
G01*
X114162531Y-49154546D02*
X114162531Y-59554546D01*
X213862531Y-11454546D02*
G75*
G02*
X214362531Y-10954531I499969J46D01*
G01*
X127562531Y-10954546D02*
X137962531Y-10954546D01*
X139462531Y-11454546D02*
G75*
G02*
X139962531Y-10954531I499969J46D01*
G01*
X114162531Y-11954546D02*
G75*
G02*
X115162531Y-10954531I999969J46D01*
G01*
X115162531Y-134954546D02*
G75*
G02*
X114162454Y-133954546I-31J1000046D01*
G01*
X214362531Y-134954546D02*
G75*
G02*
X213862454Y-134454546I-31J500046D01*
G01*
X114162531Y-61554546D02*
X114162531Y-71954546D01*
X175662531Y-134454546D02*
G75*
G02*
X175162531Y-134954531I-500031J46D01*
G01*
X189062531Y-11454546D02*
G75*
G02*
X189562531Y-10954531I499969J46D01*
G01*
X114662531Y-109654546D02*
G75*
G02*
X114162454Y-109154546I-31J500046D01*
G01*
X114162531Y-86354546D02*
X114162531Y-96754546D01*
X237662531Y-48654546D02*
G75*
G02*
X238162454Y-49154546I-31J-499954D01*
G01*
X177162531Y-134954546D02*
G75*
G02*
X176662454Y-134454546I-31J500046D01*
G01*
X237662531Y-61054546D02*
G75*
G02*
X238162454Y-61554546I-31J-499954D01*
G01*
X226262531Y-11454546D02*
G75*
G02*
X226762531Y-10954531I499969J46D01*
G01*
X152362531Y-134954546D02*
X162762531Y-134954546D01*
X238162531Y-96754546D02*
G75*
G02*
X237662531Y-97254531I-500031J46D01*
G01*
X176662531Y-11454546D02*
G75*
G02*
X177162531Y-10954531I499969J46D01*
G01*
X114162531Y-36754546D02*
X114162531Y-47154546D01*
X238162531Y-98754546D02*
X238162531Y-109154546D01*
X238162531Y-133954546D02*
G75*
G02*
X237162531Y-134954531I-1000031J46D01*
G01*
X226762531Y-134954546D02*
G75*
G02*
X226262454Y-134454546I-31J500046D01*
G01*
X114662531Y-122054546D02*
G75*
G02*
X114162454Y-121554546I-31J500046D01*
G01*
X114162531Y-49154546D02*
G75*
G02*
X114662531Y-48654531I499969J46D01*
G01*
X237662531Y-110654546D02*
G75*
G02*
X238162454Y-111154546I-31J-499954D01*
G01*
X238162531Y-59554546D02*
G75*
G02*
X237662531Y-60054531I-500031J46D01*
G01*
X224762531Y-10954546D02*
G75*
G02*
X225262454Y-11454546I-31J-499954D01*
G01*
X226762531Y-134954546D02*
X237162531Y-134954546D01*
X238162531Y-109154546D02*
G75*
G02*
X237662531Y-109654531I-500031J46D01*
G01*
X238162531Y-73954546D02*
X238162531Y-84354546D01*
X114662531Y-60054546D02*
G75*
G02*
X114162454Y-59554546I-31J500046D01*
G01*
X214362531Y-134954546D02*
X224762531Y-134954546D01*
D12*
X235437531Y-14204546D02*
G75*
G02*
X234387531Y-14204546I-525000J0D01*
G01*
X234387531Y-14204546D02*
G75*
G02*
X235437531Y-14204546I525000J0D01*
G01*
D10*
X177162531Y-134954546D02*
X187562531Y-134954546D01*
X114162531Y-24354546D02*
G75*
G02*
X114662531Y-23854531I499969J46D01*
G01*
D12*
X235437531Y-131704546D02*
G75*
G02*
X234387531Y-131704546I-525000J0D01*
G01*
X234387531Y-131704546D02*
G75*
G02*
X235437531Y-131704546I525000J0D01*
G01*
D10*
X164762531Y-10954546D02*
X175162531Y-10954546D01*
X164262531Y-11454546D02*
G75*
G02*
X164762531Y-10954531I499969J46D01*
G01*
X114162531Y-123554546D02*
X114162531Y-133954546D01*
X114662531Y-47654546D02*
G75*
G02*
X114162454Y-47154546I-31J500046D01*
G01*
D11*
X235412531Y-43579546D02*
G75*
G02*
X234412531Y-43579546I-500000J0D01*
G01*
X234412531Y-43579546D02*
G75*
G02*
X235412531Y-43579546I500000J0D01*
G01*
D10*
X114662531Y-22854546D02*
G75*
G02*
X114162454Y-22354546I-31J500046D01*
G01*
X238162531Y-47154546D02*
G75*
G02*
X237662531Y-47654531I-500031J46D01*
G01*
X151862531Y-11454546D02*
G75*
G02*
X152362531Y-10954531I499969J46D01*
G01*
X127562531Y-134954546D02*
X137962531Y-134954546D01*
D11*
X147287531Y-131704546D02*
G75*
G02*
X146287531Y-131704546I-500000J0D01*
G01*
X146287531Y-131704546D02*
G75*
G02*
X147287531Y-131704546I500000J0D01*
G01*
D10*
X114162531Y-36754546D02*
G75*
G02*
X114662531Y-36254531I499969J46D01*
G01*
X189562531Y-134954546D02*
X199962531Y-134954546D01*
D11*
X117912531Y-102329546D02*
G75*
G02*
X116912531Y-102329546I-500000J0D01*
G01*
X116912531Y-102329546D02*
G75*
G02*
X117912531Y-102329546I500000J0D01*
G01*
X206037531Y-14204546D02*
G75*
G02*
X205037531Y-14204546I-500000J0D01*
G01*
X205037531Y-14204546D02*
G75*
G02*
X206037531Y-14204546I500000J0D01*
G01*
D10*
X162762531Y-10954546D02*
G75*
G02*
X163262454Y-11454546I-31J-499954D01*
G01*
X189562531Y-10954546D02*
X199962531Y-10954546D01*
X152362531Y-134954546D02*
G75*
G02*
X151862454Y-134454546I-31J500046D01*
G01*
X238162531Y-22354546D02*
G75*
G02*
X237662531Y-22854531I-500031J46D01*
G01*
X125562531Y-10954546D02*
G75*
G02*
X126062454Y-11454546I-31J-499954D01*
G01*
X238162531Y-49154546D02*
X238162531Y-59554546D01*
X212362531Y-10954546D02*
G75*
G02*
X212862454Y-11454546I-31J-499954D01*
G01*
X114162531Y-111154546D02*
G75*
G02*
X114662531Y-110654531I499969J46D01*
G01*
X127062531Y-11454546D02*
G75*
G02*
X127562531Y-10954531I499969J46D01*
G01*
X214362531Y-10954546D02*
X224762531Y-10954546D01*
X114162531Y-98754546D02*
G75*
G02*
X114662531Y-98254531I499969J46D01*
G01*
X200462531Y-134454546D02*
G75*
G02*
X199962531Y-134954531I-500031J46D01*
G01*
X150862531Y-134454546D02*
G75*
G02*
X150362531Y-134954531I-500031J46D01*
G01*
D11*
X235412531Y-102329546D02*
G75*
G02*
X234412531Y-102329546I-500000J0D01*
G01*
X234412531Y-102329546D02*
G75*
G02*
X235412531Y-102329546I500000J0D01*
G01*
D10*
X238162531Y-121554546D02*
G75*
G02*
X237662531Y-122054531I-500031J46D01*
G01*
X188062531Y-134454546D02*
G75*
G02*
X187562531Y-134954531I-500031J46D01*
G01*
X114662531Y-97254546D02*
G75*
G02*
X114162454Y-96754546I-31J500046D01*
G01*
X114162531Y-61554546D02*
G75*
G02*
X114662531Y-61054531I499969J46D01*
G01*
D13*
G04 #@! TO.C,D2*
X196646800Y-50786802D03*
X196646800Y-46786800D03*
G04 #@! TD*
D14*
G04 #@! TO.C,R24*
X193903600Y-85230200D03*
X193903600Y-86880200D03*
G04 #@! TD*
D15*
G04 #@! TO.C,U3*
X192043900Y-83581201D03*
X192043900Y-84531200D03*
X192043900Y-85481199D03*
X189768900Y-85481199D03*
X189768900Y-83581201D03*
G04 #@! TD*
D16*
G04 #@! TO.C,C25*
X180480000Y-82904999D03*
X180480000Y-85855001D03*
G04 #@! TD*
D17*
G04 #@! TO.C,R27*
X191883800Y-87020400D03*
X190233800Y-87020400D03*
G04 #@! TD*
D18*
G04 #@! TO.C,R26*
X196951600Y-82296000D03*
X196951600Y-80646000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R28*
X199262163Y-53874237D03*
X198095437Y-55040963D03*
G04 #@! TD*
D16*
G04 #@! TO.C,C23*
X199820000Y-47194999D03*
X199820000Y-50145001D03*
G04 #@! TD*
D20*
G04 #@! TO.C,R25*
X195364600Y-83769200D03*
X197014600Y-83769200D03*
G04 #@! TD*
D21*
G04 #@! TO.C,C21*
X189331600Y-81539200D03*
X189331600Y-82499200D03*
G04 #@! TD*
D22*
G04 #@! TO.C,U7*
X198808100Y-83957200D03*
X198808100Y-82057200D03*
X200683101Y-83007200D03*
G04 #@! TD*
D16*
G04 #@! TO.C,C19*
X186490000Y-82924999D03*
X186490000Y-85875001D03*
G04 #@! TD*
D23*
G04 #@! TO.C,C22*
X190347600Y-82268000D03*
X190347600Y-81308000D03*
G04 #@! TD*
D24*
G04 #@! TO.C,R2*
X197790637Y-40920237D03*
X198957363Y-42086963D03*
G04 #@! TD*
D18*
G04 #@! TO.C,R22*
X193903600Y-83832200D03*
X193903600Y-82182200D03*
G04 #@! TD*
M02*
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+568
View File
@@ -0,0 +1,568 @@
// Generated by SolidPython <Unknown> on 2025-09-08 06:43:34
scale(v = 0.0000010000) {
translate(v = [-176162531, -72954546, 0]) {
difference() {
linear_extrude(convexity = 10, height = 2850000) {
polygon(points = [[131213899.2582568675, 51504136.1925114766], [131322961.1450932473, 51602983.4876628667], [131427033.4434460700, 51707053.7829912156], [131525874.5944232941, 51816104.6726151481], [131619246.5432596207, 51929873.5120421499], [131706924.4075352252, 52048086.2937974632], [131788697.0187926441, 52170458.3073561117], [131864367.4311287850, 52296694.8248580173], [131933753.3955310285, 52426491.8109482452], [131996687.7988158464, 52559536.6550326645], [132053019.0661133975, 52695508.9241863117], [132102611.5259304792, 52834081.1349053234], [132145345.7369083166, 52974919.5418341309], [132181118.7754931301, 53117684.9415800571], [132209844.4838224947, 53262033.4896680042], [132231453.6772334278, 53407617.5286776870], [132245894.3108902276, 53554086.4255596548], [132253131.6051326990, 53701087.4161216170], [132253148.1292413920, 53848266.4546466470], [132245943.8434186429, 53995269.0665961355], [132231536.0988845825, 54141741.2023476064], [132209959.5960872918, 54287330.0899088308], [132181266.3011285216, 54431685.0845522285], [132145525.3206049204, 54574458.5133318007], [132102822.7351685613, 54715306.5124343708], [132053261.3922043890, 54853889.8553650752], [131996960.6581275314, 54989874.7699568421], [131934056.1308933049, 55122933.7422496080], [131864699.3134158701, 55252746.3052921742], [131789057.2486802191, 55378999.8109728545], [131707312.1174248457, 55501390.1830215976], [131619660.7993682474, 55619622.6493629590], [131526314.3990321904, 55733412.4520627260], [131427497.7373047322, 55842485.5331561118], [131323448.8099688888, 55946579.1947044656], [131214409.1214805245, 56045450.9765909687], [131144127.0656185150, 56106104.2244711742], [131144108.6774217337, 56106120.4915215373], [130980510.8611069918, 56254466.1425165683], [130980492.8777911961, 56254482.8560757861], [130953885.3395007998, 56279827.5169951245], [130953850.6306188852, 56279862.2430811599], [130928519.1580728292, 56306482.3375511691], [130928502.4534272254, 56306500.3291470855], [130780237.8909005970, 56470171.6374789476], [130780221.6329644322, 56470190.0337345377], [130756227.2904007584, 56498021.3911554590], [130756196.1535789669, 56498059.3530556262], [130733596.7819408923, 56527034.8829472438], [130733581.9218227565, 56527054.4257105514], [130602079.3822011352, 56704474.1533214524], [130602065.0063161254, 56704494.0549957156], [130580915.0838191807, 56734543.9087273777], [130580887.8191112131, 56734584.7406172901], [130561238.3299171031, 56765636.4797864035], [130561225.4575277567, 56765657.3853909448], [130447752.1806984991, 56955115.8059417307], [130447739.8253995627, 56955137.0212498009], [130429638.1367162168, 56987115.7922980860], [130429615.0068616718, 56987159.1006963924], [130413104.7547553778, 57019987.8153588995], [130413093.9941411167, 57020009.8823453113], [130318743.4788517952, 57219681.2572004274], [130318733.2632022500, 57219703.5816984698], [130303854.2474116236, 57253303.1028273180], [130303835.4753040969, 57253348.4703874663], [130290623.5632968098, 57287637.8027235493], [130290615.0181535929, 57287660.8184406981], [130216296.4843825400, 57495620.9906144068], [130216288.5068267137, 57495644.2091697305], [130204775.5473372191, 57530540.6945905685], [130204761.3138763607, 57530587.6841226742], [130194975.0601314753, 57566007.2008976862], [130194968.8128055781, 57566030.9435518682], [130141398.4399561137, 57780275.8800737187], [130141392.7773706615, 57780299.7689379528], [130133356.8201874495, 57816156.9345130622], [130133347.2625357211, 57816205.0931968987], [130127080.9734529555, 57852413.4700194299], [130127077.0841474831, 57852437.7108117267], [130094771.1094986349, 58070902.8148307130], [130094767.8164517432, 58070927.1437959746], [130090286.3010333180, 58107399.4476787969], [130090281.5112941563, 58107448.3114274666], [130087595.5727148652, 58144096.6219602451], [130087594.0789095610, 58144121.1272913814], [130076863.8228337765, 58364701.1338128000], [130076862.9310592860, 58364725.6684301123], [130075092.9080227315, 58438291.0878120512], [130075092.6188165694, 58438315.6369274780], [130079560.3298669159, 67454047.5810131580], [130076018.2738393843, 67601261.9571875334], [130065255.6406022161, 67748112.7928943783], [130047298.0792614371, 67894258.3648860902], [130022188.8784497529, 68039346.3735452592], [129989988.5665958524, 68183027.0686249435], [129950774.7660140246, 68324954.0923593193], [129904642.0057877898, 68464785.3143943846], [129851701.4938959628, 68602183.6565344334], [129792080.8491359353, 68736817.9052991718], [129725923.7934830189, 68868363.5103545785], [129653389.8056340814, 68996503.3668738455], [129574653.7365666628, 69120928.5799535215], [129489905.3880423903, 69241339.2092369944], [129399349.0550684780, 69357444.9919544607], [129303203.0334239006, 69468966.0426308215], [129201699.0934326351, 69575633.5277812481], [129095081.9212570786, 69677190.3139625043], [128983608.5290567279, 69773391.5876197666], [128867547.6354295462, 69864005.4452389181], [128747179.0176410526, 6994Line truncated
}
translate(v = [0, 0, -3000000]) {
linear_extrude(convexity = 10, height = 9000000) {
polygon(points = [[134075000.0000000000, 38008758.2351448461], [134075000.0000000000, 48150000.0000000000], [139700000.0000000000, 48150000.0000000000], [139798017.1403295696, 48154815.2733278051], [139895090.3220161200, 48169214.7195967659], [139990284.6772544682, 48193059.6642677933], [140082683.4323650897, 48226120.4674887136], [140171396.7368260026, 48268078.7356516421], [140255570.2330195904, 48318530.3876974583], [140334393.2841636539, 48376989.5466372594], [140407106.7811865509, 48442893.2188134491], [140473010.4533627331, 48515606.7158363536], [140531469.6123025417, 48594429.7669803947], [140581921.2643483579, 48678603.2631740049], [140623879.5325112939, 48767316.5676349103], [140656940.3357322216, 48859715.3227455392], [140680785.2804032266, 48954909.6779838726], [140695184.7266722023, 49051982.8596704379], [140700000.0000000000, 49150000.0000000000], [140700000.0000000000, 49750000.0000000000], [140695184.7266722023, 49848017.1403295621], [140680785.2804032266, 49945090.3220161274], [140656940.3357322216, 50040284.6772544608], [140623879.5325112939, 50132683.4323650897], [140581921.2643483579, 50221396.7368259951], [140531469.6123025417, 50305570.2330196053], [140473010.4533627331, 50384393.2841636464], [140407106.7811865509, 50457106.7811865509], [140334393.2841636539, 50523010.4533627406], [140255570.2330195904, 50581469.6123025417], [140250000.0000000000, 50584808.2814897224], [140250000.0000000000, 56982940.4849924147], [140309393.2841636539, 57026989.5466372594], [140382106.7811865509, 57092893.2188134491], [140448010.4533627331, 57165606.7158363536], [140506469.6123025417, 57244429.7669803947], [140556921.2643483579, 57328603.2631740049], [140598879.5325112939, 57417316.5676349103], [140631940.3357322216, 57509715.3227455392], [140655785.2804032266, 57604909.6779838726], [140670184.7266722023, 57701982.8596704379], [140675000.0000000000, 57800000.0000000000], [140675000.0000000000, 58400000.0000000000], [140670184.7266722023, 58498017.1403295621], [140655785.2804032266, 58595090.3220161274], [140631940.3357322216, 58690284.6772544608], [140598879.5325112939, 58782683.4323650897], [140556921.2643483579, 58871396.7368259951], [140506469.6123025417, 58955570.2330196053], [140448010.4533627331, 59034393.2841636464], [140382106.7811865509, 59107106.7811865509], [140309393.2841636539, 59173010.4533627406], [140230570.2330195904, 59231469.6123025417], [140146396.7368260026, 59281921.2643483579], [140057683.4323650897, 59323879.5325112864], [139965284.6772544682, 59356940.3357322067], [139870090.3220161200, 59380785.2804032341], [139773017.1403295696, 59395184.7266721949], [139675000.0000000000, 59400000.0000000000], [134075569.6689103544, 59400000.0000000000], [134099591.7203668356, 107875914.0057790428], [218249706.8595425487, 107900333.6632250249], [218224905.8084288836, 38033177.9520300925], [134075000.0000000000, 38008758.2351448461]]);
}
}
translate(v = [0, 0, 1400000]) {
linear_extrude(convexity = 10, height = 3000000) {
polygon(points = [[133025000.0000000000, 37008468.0000000000], [133025000.0000000000, 49150000.0000000000], [133025240.7636663914, 49154900.8570164815], [133025960.7359798402, 49159754.5161008090], [133027152.9832133949, 49164514.2338627204], [133028806.0233744383, 49169134.1716182530], [133030903.9367825836, 49173569.8368413001], [133033426.5193848759, 49177778.5116509795], [133036349.4773318619, 49181719.6642081812], [133039644.6609406769, 49185355.3390593305], [133043280.3357918113, 49188650.5226681381], [133047221.4883490205, 49191573.4806151241], [133051430.1631586999, 49194096.0632174164], [133055865.8283817470, 49196193.9766255617], [133060485.7661372721, 49197847.0167866126], [133065245.4838991910, 49199039.2640201598], [133070099.1429835260, 49199759.2363336086], [133075000.0000000000, 49200000.0000000000], [139650000.0000000000, 49200000.0000000000], [139650000.0000000000, 49700000.0000000000], [139250000.0000000000, 49700000.0000000000], [139245099.1429835260, 49700240.7636663914], [139240245.4838992059, 49700960.7359798402], [139235485.7661372721, 49702152.9832133874], [139230865.8283817470, 49703806.0233744383], [139226430.1631587148, 49705903.9367825836], [139222221.4883490205, 49708426.5193848759], [139218280.3357918262, 49711349.4773318619], [139214644.6609406769, 49714644.6609406695], [139211349.4773318768, 49718280.3357918188], [139208426.5193848610, 49722221.4883490205], [139205903.9367825687, 49726430.1631586999], [139203806.0233744383, 49730865.8283817470], [139202152.9832133949, 49735485.7661372796], [139200960.7359798253, 49740245.4838991910], [139200240.7636663914, 49745099.1429835185], [139200000.0000000000, 49750000.0000000000], [139200000.0000000000, 57800000.0000000000], [139200240.7636663914, 57804900.8570164815], [139200960.7359798253, 57809754.5161008090], [139202152.9832133949, 57814514.2338627204], [139203806.0233744383, 57819134.1716182530], [139205903.9367825687, 57823569.8368413001], [139208426.5193848610, 57827778.5116509795], [139211349.4773318768, 57831719.6642081812], [139214644.6609406769, 57835355.3390593305], [139218280.3357918262, 57838650.5226681381], [139222221.4883490205, 57841573.4806151241], [139226430.1631587148, 57844096.0632174164], [139230865.8283817470, 57846193.9766255617], [139235485.7661372721, 57847847.0167866126], [139240245.4838992059, 57849039.2640201598], [139245099.1429835260, 57849759.2363336086], [139250000.0000000000, 57850000.0000000000], [139625000.0000000000, 57850000.0000000000], [139625000.0000000000, 58350000.0000000000], [133075074.0000000000, 58350000.0000000000], [133070171.6018611938, 58350240.9154777899], [133065316.4462659806, 58350961.3403004557], [133060555.3204995692, 58352154.3320083842], [133055934.1057175100, 58353808.3941948861], [133051497.3348064721, 58355907.5872926787], [133047287.7632378340, 58358431.6821774468], [133043345.9570494145, 58361356.3551081717], [133039709.9019260556, 58364653.4221258014], [133036414.6371460706, 58368291.1106512994], [133033491.9179209918, 58372234.3656658977], [133030969.9093827754, 58376445.1875229701], [133028872.9151671082, 58380882.9981361181], [133027221.1432085037, 58385505.0320147872], [133026030.5110041946, 58390266.7483789697], [133025312.4922232479, 58395122.2603817284], [133025074.0061391443, 58400024.7773033231], [133050087.0061391443, 108875648.7773033231], [133050329.9565512538, 108880547.0709154904], [133051051.6156015694, 108885398.0000459552], [133052245.0402646214, 108890154.8942835182], [133053898.7486947030, 108894771.9879193306], [133055996.8306918591, 108899204.8602553010], [133058519.1007726640, 108903410.8629720360], [133061441.2923731953, 108907349.5304451138], [133064735.2913157493, 108910982.9690616578], [133068369.4062931240, 108914276.2217918485], [133072308.6737683117, 108917197.6045077294], [133076515.1943558156, 108919719.0108138472], [133080948.4974488616, 108921816.1824565530], [133085565.9305837750, 108923468.9427108914], [133090323.0697961301, 108924661.3904992342], [133095174.1470202357, 108925382.0533742905], [133100072.4904200733, 108925623.9978947192], [219250047.4904200733, 108950623.9978947192], [219254950.4235864878, 108950384.4593511671], [219259806.2399122119, 108949665.3069619238], [219264568.1368401051, 108948473.4722434431], [219269190.2170507610, 108946820.4426385760], [219273627.9308415353, 108944722.1507953852], [219277838.5055169165, 108942198.8210010380], [219281781.3576510847, 108939274.7742510140], [219285418.4842492640, 108935978.1938323081], [219288714.8290379047, 108932340.8536801338], [219291638.6203528941, 108928397.8121263981], [219294161.6773692369, 108924187.0739915371], [219296259.6817206144, 108919749.2242769003], [219297912.4118908346, 108915127.0369881094], [219299103.9381178916, 108910365.0628598481], [219299822.7759324014, 108905509.1999556273], [219300061.9968498349, 108900606.2512815297], [219274550.9968498349, 37033450.2512815222], [219274308.6918088198, 37028551.4864123166], [219273587.5862336457, 37023700.03140Line truncated
}
}
translate(v = [176162531, 125454546, 2850000]) {
union() {
cylinder(d = 4500000, h = 300000);
translate(v = [0, 0, -2250000.0]) {
cylinder(d1 = 0, d2 = 4500000, h = 2250000.0);
}
translate(v = [0, 0, -10000000]) {
cylinder(d = 2000000, h = 10000000);
}
}
}
translate(v = [176162531, 20454546, 2850000]) {
union() {
cylinder(d = 4500000, h = 300000);
translate(v = [0, 0, -2250000.0]) {
cylinder(d1 = 0, d2 = 4500000, h = 2250000.0);
}
translate(v = [0, 0, -10000000]) {
cylinder(d = 2000000, h = 10000000);
}
}
}
translate(v = [228662531, 72954546, 2850000]) {
union() {
cylinder(d = 4500000, h = 300000);
translate(v = [0, 0, -2250000.0]) {
cylinder(d1 = 0, d2 = 4500000, h = 2250000.0);
}
translate(v = [0, 0, -10000000]) {
cylinder(d = 2000000, h = 10000000);
}
}
}
translate(v = [123662531, 72954546, 2850000]) {
union() {
cylinder(d = 4500000, h = 300000);
translate(v = [0, 0, -2250000.0]) {
cylinder(d1 = 0, d2 = 4500000, h = 2250000.0);
}
translate(v = [0, 0, -10000000]) {
cylinder(d = 2000000, h = 10000000);
}
}
}
}
}
}
/***********************************************
********* SolidPython code: **********
************************************************
from pcbnewTransition import pcbnew
import numpy as np
import json
from collections import OrderedDict
from kikit.common import *
from kikit.defs import *
from kikit.substrate import Substrate, extractRings, toShapely, linestringToKicad
from kikit.export import gerberImpl, pasteDxfExport
from kikit.export import exportSettingsJlcpcb
# Allow importing solid from this dir
import os
import sys
cur_path = os.path.abspath(os.path.dirname(__file__))
if cur_path not in sys.path:
sys.path.insert(0, cur_path)
import solid
import solid.utils
import subprocess
import shutil
from kikit.common import removeComponents, parseReferences
from shapely.geometry import Point
OUTER_BORDER = fromMm(7.5)
INNER_BORDER = fromMm(5)
MOUNTING_HOLES_COUNT = 3
MOUNTING_HOLE_R = fromMm(1)
HOLE_SPACING = fromMm(20)
def addBottomCounterpart(board, item):
item = item.Duplicate()
item.SetLayer(Layer.B_Paste)
board.Add(item)
def addRoundedCorner(board, center, start, end, thickness):
corner = pcbnew.PCB_SHAPE()
corner.SetShape(STROKE_T.S_ARC)
corner.SetCenter(toKiCADPoint((center[0], center[1])))
corner.SetStart(toKiCADPoint((start[0], start[1])))
if np.cross(start - center, end - center) > 0:
corner.SetArcAngleAndEnd(fromDegrees(90), True)
else:
corner.SetArcAngleAndEnd(fromDegrees(-90), True)
corner.SetWidth(thickness)
corner.SetLayer(Layer.F_Paste)
board.Add(corner)
addBottomCounterpart(board, corner)
def addLine(board, start, end, thickness):
line = pcbnew.PCB_SHAPE()
line.SetShape(STROKE_T.S_SEGMENT)
line.SetStart(toKiCADPoint((start[0], start[1])))
line.SetEnd(toKiCADPoint((end[0], end[1])))
line.SetWidth(thickness)
line.SetLayer(Layer.F_Paste)
board.Add(line)
addBottomCounterpart(board, line)
def addBite(board, origin, direction, normal, thickness):
"""
Adds a bite to the stencil, direction points to the bridge, normal points
inside the stencil
"""
direction = normalize(direction) * thickness
normal = normalize(normal) * thickness
center = toKiCADPoint((origin[0], origin[1])) + toKiCADPoint((normal[0], normal[1]))
start = origin
end = center + toKiCADPoint((direction[0], direction[1]))
# addLine(board, end, end + normal / 2, thickness)
addRoundedCorner(board, center, start, end, thickness)
def numberOfCuts(length, bridgeWidth, bridgeSpacing):
"""
Return number of bridges which fit inside the length and cut length
"""
count = round(np.floor((length + bridgeWidth) / (bridgeWidth + bridgeSpacing)))
cutLength = round((length - (count - 1) * bridgeWidth) / count)
return count, cutLength
def addFrame(board, rect, bridgeWidth, bridgeSpacing, clearance):
"""
Add rectangular frame to the board
"""
R=fromMm(1)
corners = [
(tl(rect), toKiCADPoint((R, 0)), toKiCADPoint((0, R))), # TL
(tr(rect), toKiCADPoint((0, R)), toKiCADPoint((-R, 0))), # TR
(br(rect), toKiCADPoint((-R, 0)), toKiCADPoint((0, -R))), # BR
(bl(rect), toKiCADPoint((0, -R)), toKiCADPoint((R, 0))) # BL
]
for c, sOffset, eOffset in corners:
addRoundedCorner(board, c + sOffset + eOffset, c + sOffset, c + eOffset, clearance)
count, cutLength = numberOfCuts(rect.GetWidth() - 2 * R, bridgeWidth, bridgeSpacing)
for i in range(count):
start = round(rect.GetX() + R + i * bridgeWidth + i * cutLength)
end = start + cutLength
y1, y2 = rect.GetY(), rect.GetY() + rect.GetHeight()
addLine(board, toKiCADPoint((start, y1)), toKiCADPoint((end, y1)), clearance)
if i != 0:
addBite(board, toKiCADPoint((start, y1)), toKiCADPoint((-1, 0)), toKiCADPoint((0, 1)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((end, y1)), toKiCADPoint((1, 0)), toKiCADPoint((0, 1)), clearance)
addLine(board, toKiCADPoint((start, y2)), toKiCADPoint((end, y2)), clearance)
if i != 0:
addBite(board, toKiCADPoint((start, y2)), toKiCADPoint((-1, 0)), toKiCADPoint((0, -1)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((end, y2)), toKiCADPoint((1, 0)), toKiCADPoint((0, -1)), clearance)
count, cutLength = numberOfCuts(rect.GetHeight() - 2 * R, bridgeWidth, bridgeSpacing)
for i in range(count):
start = rect.GetY() + R + i * bridgeWidth + i * cutLength
end = start + cutLength
x1, x2 = rect.GetX(), rect.GetX() + rect.GetWidth()
addLine(board, toKiCADPoint((x1, start)), toKiCADPoint((x1, end)), clearance)
if i != 0:
addBite(board, toKiCADPoint((x1, start)), toKiCADPoint((0, -1)), toKiCADPoint((1, 0)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((x1, end)), toKiCADPoint((0, 1)), toKiCADPoint((1, 0)), clearance)
addLine(board, toKiCADPoint((x2, start)), toKiCADPoint((x2, end)), clearance)
if i != 0:
addBite(board, toKiCADPoint((x2, start)), toKiCADPoint((0, -1)), toKiCADPoint((-1, 0)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((x2, end)), toKiCADPoint((0, 1)), toKiCADPoint((-1, 0)), clearance)
def addHole(board, position, radius):
circle = pcbnew.PCB_SHAPE()
circle.SetShape(STROKE_T.S_CIRCLE)
circle.SetCenter(toKiCADPoint((position[0], position[1])))
# Set 3'oclock point of the circle to set radius
circle.SetEnd(toKiCADPoint((position[0], position[1])) + toKiCADPoint((radius/2, 0)))
circle.SetWidth(radius)
circle.SetLayer(Layer.F_Paste)
board.Add(circle)
addBottomCounterpart(board, circle)
def addJigFrame(board, jigFrameSize, bridgeWidth=fromMm(2),
bridgeSpacing=fromMm(10), clearance=fromMm(0.5)):
"""
Given a Pcbnew board finds the board outline and creates a stencil for
KiKit's stencil jig.
Mainly, adds mounting holes and mouse bites to define the panel outline.
jigFrameSize is a tuple (width, height).
"""
bBox = findBoardBoundingBox(board)
frameSize = rectByCenter(rectCenter(bBox),
jigFrameSize[0] + 2 * (OUTER_BORDER + INNER_BORDER),
jigFrameSize[1] + 2 * (OUTER_BORDER + INNER_BORDER))
cutSize = rectByCenter(rectCenter(bBox),
jigFrameSize[0] + 2 * (OUTER_BORDER + INNER_BORDER) - fromMm(1),
jigFrameSize[1] + 2 * (OUTER_BORDER + INNER_BORDER) - fromMm(1))
addFrame(board, cutSize, bridgeWidth, bridgeSpacing, clearance)
for i in range(MOUNTING_HOLES_COUNT):
x = frameSize.GetX() + OUTER_BORDER / 2 + (i + 1) * (frameSize.GetWidth() - OUTER_BORDER) / (MOUNTING_HOLES_COUNT + 1)
addHole(board, toKiCADPoint((x, OUTER_BORDER / 2 + frameSize.GetY())), MOUNTING_HOLE_R)
addHole(board, toKiCADPoint((x, - OUTER_BORDER / 2 +frameSize.GetY() + frameSize.GetHeight())), MOUNTING_HOLE_R)
for i in range(MOUNTING_HOLES_COUNT):
y = frameSize.GetY() + OUTER_BORDER / 2 + (i + 1) * (frameSize.GetHeight() - OUTER_BORDER) / (MOUNTING_HOLES_COUNT + 1)
addHole(board, toKiCADPoint((OUTER_BORDER / 2 + frameSize.GetX(), y)), MOUNTING_HOLE_R)
addHole(board, toKiCADPoint((- OUTER_BORDER / 2 +frameSize.GetX() + frameSize.GetWidth(), y)), MOUNTING_HOLE_R)
PIN_TOLERANCE = fromMm(0.05)
addHole(board, tl(frameSize) + toKiCADPoint((OUTER_BORDER / 2, OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, tr(frameSize) + toKiCADPoint((-OUTER_BORDER / 2, OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, br(frameSize) + toKiCADPoint((-OUTER_BORDER / 2, -OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, bl(frameSize) + toKiCADPoint((OUTER_BORDER / 2, -OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
def jigMountingHoles(jigFrameSize, origin=toKiCADPoint((0, 0))):
""" Get list of all mounting holes in a jig of given size """
w, h = jigFrameSize
holes = [
toKiCADPoint((0, (w + INNER_BORDER) / 2)),
toKiCADPoint((0, -(w + INNER_BORDER) / 2)),
toKiCADPoint(((h + INNER_BORDER) / 2, 0)),
toKiCADPoint((-(h + INNER_BORDER) / 2, 0)),
]
return [x + origin for x in holes]
def createOuterPolygon(board, jigFrameSize, outerBorder):
bBox = findBoardBoundingBox(board)
centerpoint = rectCenter(bBox)
holes = jigMountingHoles(jigFrameSize, centerpoint)
outerSubstrate = Substrate(collectEdges(board, Layer.Edge_Cuts))
outerSubstrate.substrates = outerSubstrate.substrates.buffer(outerBorder)
tabs = []
for hole in holes:
tab, _ = outerSubstrate.tab(hole, centerpoint - hole, INNER_BORDER, maxHeight=fromMm(1000))
tabs.append(tab)
outerSubstrate.union(tabs)
outerSubstrate.union([Point(x).buffer(INNER_BORDER / 2) for x in holes])
outerSubstrate.millFillets(fromMm(3))
return outerSubstrate.exterior(), holes
def createOffsetPolygon(board, offset):
outerSubstrate = Substrate(collectEdges(board, Layer.Edge_Cuts))
outerSubstrate.substrates = outerSubstrate.substrates.buffer(offset)
return outerSubstrate.exterior()
def m2countersink():
HEAD_DIA = fromMm(4.5)
HOLE_LEN = fromMm(10)
SINK_EXTRA = fromMm(0.3)
sinkH = np.sqrt(HEAD_DIA**2 / 4)
sink = solid.cylinder(d1=0, d2=HEAD_DIA, h=sinkH)
sinkE = solid.cylinder(d=HEAD_DIA, h=SINK_EXTRA)
hole = solid.cylinder(h=HOLE_LEN, d=fromMm(2))
return sinkE + solid.utils.down(sinkH)(sink) + solid.utils.down(HOLE_LEN)(hole)
def mirrorX(linestring, origin):
return [(2 * origin - x, y) for x, y in linestring]
def makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, topSide):
bBox = findBoardBoundingBox(board)
centerpoint = rectCenter(bBox)
top = jigThickness - fromMm(0.15)
pcbBottom = jigThickness - pcbThickness
outerPolygon, holes = createOuterPolygon(board, jigFrameSize, outerBorder)
outerRing = outerPolygon.exterior.coords
if topSide:
outerRing = mirrorX(outerRing, centerpoint[0])
body = solid.linear_extrude(height=top, convexity=10)(solid.polygon(
outerRing))
innerRings = [x.exterior.coords for x in listGeometries(createOffsetPolygon(board, - innerBorder))]
if topSide:
innerRings = [mirrorX(innerRing, centerpoint[0]) for innerRing in innerRings]
innerCutout = solid.utils.down(jigThickness)(
solid.linear_extrude(height=3 * jigThickness, convexity=10)(solid.polygon(innerRings[0])))
for innerRing in innerRings[1:]:
innerCutout = innerCutout + solid.utils.down(jigThickness)(
solid.linear_extrude(height=3 * jigThickness, convexity=10)(solid.polygon(innerRing)))
registerRing = createOffsetPolygon(board, tolerance).exterior.coords
if topSide:
registerRing = mirrorX(registerRing, centerpoint[0])
registerCutout = solid.utils.up(jigThickness - pcbThickness)(
solid.linear_extrude(height=jigThickness, convexity=10)(solid.polygon(registerRing)))
register = body - innerCutout - registerCutout
for hole in holes:
register = register - solid.translate([hole[0], hole[1], top])(m2countersink())
return solid.scale(toMm(1))(
solid.translate([-centerpoint[0], -centerpoint[1], 0])(register))
def makeTopRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder=fromMm(3), innerBorder=fromMm(1),
tolerance=fromMm(0.05)):
"""
Create a SolidPython representation of the top register
"""
return makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, True)
def makeBottomRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder=fromMm(3), innerBorder=fromMm(1),
tolerance=fromMm(0.05)):
"""
Create a SolidPython representation of the top register
"""
return makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, False)
def renderScad(infile, outfile):
infile = os.path.abspath(infile)
outfile = os.path.abspath(outfile)
try:
subprocess.run(["openscad", "-o", outfile, infile],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as e:
message = f"Cannot render {outfile}, OpenSCAD error:\n"
message += (e.stdout.decode("utf-8") + "\n") if e.stdout is not None else ""
message += (e.stderr.decode("utf-8") + "\n") if e.stderr is not None else ""
raise RuntimeError(message)
except FileNotFoundError as e:
message = f"OpenSCAD is not available.\n"
message += f"Did you install it? Program `openscad` has to be in PATH"
raise RuntimeError(message)
def shapelyToSHAPE_POLY_SET(polygon):
p = pcbnew.SHAPE_POLY_SET()
p.AddOutline(linestringToKicad(polygon.exterior))
return p
def cutoutComponents(board, components):
topCutout = extractComponentPolygons(components, pcbnew.F_CrtYd)
for polygon in topCutout:
zone = pcbnew.PCB_SHAPE()
zone.SetShape(STROKE_T.S_POLYGON)
zone.SetPolyShape(shapelyToSHAPE_POLY_SET(polygon))
zone.SetLayer(Layer.F_Paste)
board.Add(zone)
bottomCutout = extractComponentPolygons(components, pcbnew.B_CrtYd)
for polygon in bottomCutout:
zone = pcbnew.PCB_SHAPE()
zone.SetShape(STROKE_T.S_POLYGON)
zone.SetPolyShape(shapelyToSHAPE_POLY_SET(polygon))
zone.SetLayer(Layer.B_Paste)
board.Add(zone)
def setStencilLayerVisibility(boardName):
prlPath = os.path.splitext(boardName)[0] + ".kicad_prl"
try:
with open(prlPath, encoding="utf-8") as f:
# We use ordered dict, so we preserve the ordering of the keys and
# thus, formatting
prl = json.load(f, object_pairs_hook=OrderedDict)
except FileNotFoundError:
# KiCAD didn't generate project local settings, let's create an empty one
prl = {
"board": {}
}
prl["board"]["visible_layers"] = "ffc000c_7ffffffe"
prl["board"]["visible_items"] = [
1,
2,
3,
4,
9,
10,
12,
13,
21,
22,
24,
25,
26,
27,
28,
29,
30,
34,
35
]
with open(prlPath, "w", encoding="utf-8") as f:
json.dump(prl, f, indent=2)
pass
from pathlib import Path
import os
def create(inputboard, outputdir, jigsize, jigthickness, pcbthickness,
registerborder, tolerance, ignore, cutout):
board = pcbnew.LoadBoard(inputboard)
refs = parseReferences(ignore)
removeComponents(board, refs)
Path(outputdir).mkdir(parents=True, exist_ok=True)
jigsize = (fromMm(jigsize[0]), fromMm(jigsize[1]))
addJigFrame(board, jigsize)
cutoutComponents(board, getComponents(board, parseReferences(cutout)))
stencilFile = os.path.join(outputdir, "stencil.kicad_pcb")
board.Save(stencilFile)
setStencilLayerVisibility(stencilFile)
plotPlan = [
# name, id, comment
("PasteBottom", pcbnew.B_Paste, "Paste Bottom"),
("PasteTop", pcbnew.F_Paste, "Paste top"),
]
# get a copy of exportSettingsJlcpcb dictionary and
# exclude the Edge.Cuts layer for creation of stencil gerber files
exportSettings = exportSettingsJlcpcb.copy()
exportSettings["ExcludeEdgeLayer"] = True
gerberDir = os.path.join(outputdir, "gerber")
gerberImpl(stencilFile, gerberDir, plotPlan, False, exportSettings)
shutil.make_archive(os.path.join(outputdir, "gerbers"), "zip", gerberDir)
jigthickness = fromMm(jigthickness)
pcbthickness = fromMm(pcbthickness)
outerBorder, innerBorder = fromMm(registerborder[0]), fromMm(registerborder[1])
tolerance = fromMm(tolerance)
topRegister = makeTopRegister(board, jigsize,jigthickness, pcbthickness,
outerBorder, innerBorder, tolerance)
bottomRegister = makeBottomRegister(board, jigsize,jigthickness, pcbthickness,
outerBorder, innerBorder, tolerance)
topRegisterFile = os.path.join(outputdir, "topRegister.scad")
solid.scad_render_to_file(topRegister, topRegisterFile)
renderScad(topRegisterFile, os.path.join(outputdir, "topRegister.stl"))
bottomRegisterFile = os.path.join(outputdir, "bottomRegister.scad")
solid.scad_render_to_file(bottomRegister, bottomRegisterFile)
renderScad(bottomRegisterFile, os.path.join(outputdir, "bottomRegister.stl"))
def printedStencilSubstrate(outlineDxf, thickness, frameHeight, frameWidth, frameClearance):
bodyOffset = solid.utils.up(0) if frameWidth + frameClearance == 0 else solid.offset(r=frameWidth + frameClearance)
body = solid.linear_extrude(height=thickness + frameHeight)(
bodyOffset(solid.import_dxf(outlineDxf)))
boardOffset = solid.utils.up(0) if frameClearance == 0 else solid.offset(r=frameClearance)
board = solid.utils.up(thickness)(
solid.linear_extrude(height=thickness + frameHeight)(
boardOffset(solid.import_dxf(outlineDxf))))
return body - board
def getComponents(board, references):
"""
Return a list of components based on designator
"""
return [f for f in board.GetFootprints() if f.GetReference() in references]
def collectFootprintEdges(footprint, layer):
"""
Return all edges on given layer in given footprint
"""
return [e for e in footprint.GraphicalItems() if e.GetLayer() == layer]
def extractComponentPolygons(footprints, srcLayer):
"""
Return a list of shapely polygons with holes for already placed components.
The source layer defines the geometry on which the cutout is computed.
Usually it a font or back courtyard
"""
polygons = []
for f in footprints:
edges = collectFootprintEdges(f, srcLayer)
for ring in extractRings(edges):
polygons.append(toShapely(ring, edges))
return polygons
def printedStencil(outlineDxf, holesDxf, extraHoles, thickness, frameHeight, frameWidth,
frameClearance, enlargeHoles, front):
zScale = -1 if front else 1
xRotate = 180 if front else 0
substrate = solid.scale([1, 1, zScale])(printedStencilSubstrate(outlineDxf,
thickness, frameHeight, frameWidth, frameClearance))
holesOffset = solid.utils.up(0) if enlargeHoles == 0 else solid.offset(delta=enlargeHoles)
holes = solid.linear_extrude(height=4*thickness, center=True)(
holesOffset(solid.import_dxf(holesDxf)))
substrate -= holes
for h in extraHoles:
substrate -= solid.scale([toMm(1), -toMm(1), 1])(
solid.linear_extrude(height=4*thickness, center=True)(
solid.polygon(h.exterior.coords)))
return solid.rotate(a=xRotate, v=[1, 0, 0])(substrate)
def createPrinted(inputboard, outputdir, pcbthickness, thickness, framewidth,
ignore, cutout, frameclearance, enlargeholes):
"""
Create a 3D printed self-registering stencil.
"""
board = pcbnew.LoadBoard(inputboard)
refs = parseReferences(ignore)
cutoutComponents = getComponents(board, parseReferences(cutout))
removeComponents(board, refs)
Path(outputdir).mkdir(parents=True, exist_ok=True)
# We create the stencil based on DXF export. Using it avoids the necessity
# to interpret KiCAD PAD shapes which constantly change with newer and newer
# versions.
height = min(pcbthickness, max(0.5, pcbthickness - 0.3))
bottomPaste, topPaste, outline = pasteDxfExport(board, outputdir)
# On Windows, OpenSCAD requires to use forward slashes instead of backslashes,
# hence, the replacement:
if os.name == "nt":
bottomPaste = bottomPaste.replace("\\", "/")
topPaste = topPaste.replace("\\", "/")
outline = outline.replace("\\", "/")
topCutout = extractComponentPolygons(cutoutComponents, pcbnew.F_CrtYd)
bottomCutout = extractComponentPolygons(cutoutComponents, pcbnew.B_CrtYd)
topStencil = printedStencil(outline, topPaste, topCutout, thickness, height,
framewidth, frameclearance, enlargeholes, True)
bottomStencil = printedStencil(outline, bottomPaste, bottomCutout, thickness,
height, framewidth, frameclearance, enlargeholes, False)
bottomStencilFile = os.path.join(outputdir, "bottomStencil.scad")
solid.scad_render_to_file(bottomStencil, bottomStencilFile,
file_header=f'$fa = 0.4; $fs = 0.4;', include_orig_code=True)
renderScad(bottomStencilFile, os.path.join(outputdir, "bottomStencil.stl"))
topStencilFile = os.path.join(outputdir, "topStencil.scad")
solid.scad_render_to_file(topStencil, topStencilFile,
file_header=f'$fa = 0.4; $fs = 0.4;', include_orig_code=True)
renderScad(topStencilFile, os.path.join(outputdir, "topStencil.stl"))
************************************************/
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+568
View File
@@ -0,0 +1,568 @@
// Generated by SolidPython <Unknown> on 2025-09-08 06:43:27
scale(v = 0.0000010000) {
translate(v = [-176162531, -72954546, 0]) {
difference() {
linear_extrude(convexity = 10, height = 2850000) {
polygon(points = [[221111162.7417431474, 51504136.1925114766], [221002100.8549067378, 51602983.4876628667], [220898028.5565539300, 51707053.7829912156], [220799187.4055767059, 51816104.6726151481], [220705815.4567403793, 51929873.5120421499], [220618137.5924647748, 52048086.2937974632], [220536364.9812073708, 52170458.3073561117], [220460694.5688712001, 52296694.8248580173], [220391308.6044689715, 52426491.8109482452], [220328374.2011841536, 52559536.6550326645], [220272042.9338865876, 52695508.9241863117], [220222450.4740695357, 52834081.1349053234], [220179716.2630916834, 52974919.5418341309], [220143943.2245068550, 53117684.9415800571], [220115217.5161775053, 53262033.4896680042], [220093608.3227665722, 53407617.5286776870], [220079167.6891097724, 53554086.4255596548], [220071930.3948673010, 53701087.4161216170], [220071913.8707585931, 53848266.4546466470], [220079118.1565813422, 53995269.0665961355], [220093525.9011154175, 54141741.2023476064], [220115102.4039127231, 54287330.0899088308], [220143795.6988714933, 54431685.0845522285], [220179536.6793950796, 54574458.5133318007], [220222239.2648314238, 54715306.5124343708], [220271800.6077955961, 54853889.8553650752], [220328101.3418724537, 54989874.7699568421], [220391005.8691067100, 55122933.7422496080], [220460362.6865841150, 55252746.3052921742], [220536004.7513197660, 55378999.8109728545], [220617749.8825751543, 55501390.1830215976], [220705401.2006317377, 55619622.6493629590], [220798747.6009678245, 55733412.4520627260], [220897564.2626952529, 55842485.5331561118], [221001613.1900311112, 55946579.1947044656], [221110652.8785194755, 56045450.9765909687], [221180934.9343814850, 56106104.2244711742], [221180953.3225782514, 56106120.4915215373], [221344551.1388930082, 56254466.1425165683], [221344569.1222088039, 56254482.8560757861], [221371176.6604992151, 56279827.5169951245], [221371211.3693811297, 56279862.2430811599], [221396542.8419271708, 56306482.3375511691], [221396559.5465727746, 56306500.3291470855], [221544824.1090993881, 56470171.6374789476], [221544840.3670355678, 56470190.0337345377], [221568834.7095992565, 56498021.3911554590], [221568865.8464210331, 56498059.3530556262], [221591465.2180591226, 56527034.8829472438], [221591480.0781772435, 56527054.4257105514], [221722982.6177988648, 56704474.1533214524], [221722996.9936838746, 56704494.0549957156], [221744146.9161808193, 56734543.9087273777], [221744174.1808887720, 56734584.7406172901], [221763823.6700828969, 56765636.4797864035], [221763836.5424722433, 56765657.3853909448], [221877309.8193014860, 56955115.8059417307], [221877322.1746004224, 56955137.0212498009], [221895423.8632837832, 56987115.7922980860], [221895446.9931383133, 56987159.1006963924], [221911957.2452446222, 57019987.8153588995], [221911968.0058588982, 57020009.8823453113], [222006318.5211482048, 57219681.2572004274], [222006328.7367977500, 57219703.5816984698], [222021207.7525883913, 57253303.1028273180], [222021226.5246959031, 57253348.4703874663], [222034438.4367032051, 57287637.8027235493], [222034446.9818463922, 57287660.8184406981], [222108765.5156174600, 57495620.9906144068], [222108773.4931733012, 57495644.2091697305], [222120286.4526627660, 57530540.6945905685], [222120300.6861236393, 57530587.6841226742], [222130086.9398685098, 57566007.2008976862], [222130093.1871944070, 57566030.9435518682], [222183663.5600438714, 57780275.8800737187], [222183669.2226293385, 57780299.7689379528], [222191705.1798125505, 57816156.9345130622], [222191714.7374642789, 57816205.0931968987], [222197981.0265470445, 57852413.4700194299], [222197984.9158525169, 57852437.7108117267], [222230290.8905013800, 58070902.8148307130], [222230294.1835482717, 58070927.1437959746], [222234775.6989666820, 58107399.4476787969], [222234780.4887058437, 58107448.3114274666], [222237466.4272851348, 58144096.6219602451], [222237467.9210904241, 58144121.1272913814], [222248198.1771662235, 58364701.1338128000], [222248199.0689406991, 58364725.6684301123], [222249969.0919772685, 58438291.0878120512], [222249969.3811834455, 58438315.6369274780], [222245501.6701330841, 67454047.5810131580], [222249043.7261606157, 67601261.9571875334], [222259806.3593977690, 67748112.7928943783], [222277763.9207385778, 67894258.3648860902], [222302873.1215502620, 68039346.3735452592], [222335073.4334041476, 68183027.0686249435], [222374287.2339859605, 68324954.0923593193], [222420419.9942122102, 68464785.3143943846], [222473360.5061040521, 68602183.6565344334], [222532981.1508640647, 68736817.9052991718], [222599138.2065169811, 68868363.5103545785], [222671672.1943659186, 68996503.3668738455], [222750408.2634333372, 69120928.5799535215], [222835156.6119576097, 69241339.2092369944], [222925712.9449315071, 69357444.9919544607], [223021858.9665760994, 69468966.0426308215], [223123362.9065673649, 69575633.5277812481], [223229980.0787429214, 69677190.3139625043], [223341453.4709432721, 69773391.5876197666], [223457514.3645704389, 69864005.4452389181], [223577882.9823589325, 6994Line truncated
}
translate(v = [0, 0, -3000000]) {
linear_extrude(convexity = 10, height = 9000000) {
polygon(points = [[218250062.0000000000, 38008758.2351448461], [218250062.0000000000, 48150000.0000000000], [212625062.0000000000, 48150000.0000000000], [212527044.8596704304, 48154815.2733278051], [212429971.6779838800, 48169214.7195967659], [212334777.3227455318, 48193059.6642677933], [212242378.5676349103, 48226120.4674887136], [212153665.2631739974, 48268078.7356516421], [212069491.7669804096, 48318530.3876974583], [211990668.7158363461, 48376989.5466372594], [211917955.2188134491, 48442893.2188134491], [211852051.5466372669, 48515606.7158363536], [211793592.3876974583, 48594429.7669803947], [211743140.7356516421, 48678603.2631740049], [211701182.4674887061, 48767316.5676349103], [211668121.6642677784, 48859715.3227455392], [211644276.7195967734, 48954909.6779838726], [211629877.2733277977, 49051982.8596704379], [211625062.0000000000, 49150000.0000000000], [211625062.0000000000, 49750000.0000000000], [211629877.2733277977, 49848017.1403295621], [211644276.7195967734, 49945090.3220161274], [211668121.6642677784, 50040284.6772544608], [211701182.4674887061, 50132683.4323650897], [211743140.7356516421, 50221396.7368259951], [211793592.3876974583, 50305570.2330196053], [211852051.5466372669, 50384393.2841636464], [211917955.2188134491, 50457106.7811865509], [211990668.7158363461, 50523010.4533627406], [212069491.7669804096, 50581469.6123025417], [212075062.0000000000, 50584808.2814897224], [212075062.0000000000, 56982940.4849924147], [212015668.7158363461, 57026989.5466372594], [211942955.2188134491, 57092893.2188134491], [211877051.5466372669, 57165606.7158363536], [211818592.3876974583, 57244429.7669803947], [211768140.7356516421, 57328603.2631740049], [211726182.4674887061, 57417316.5676349103], [211693121.6642677784, 57509715.3227455392], [211669276.7195967734, 57604909.6779838726], [211654877.2733277977, 57701982.8596704379], [211650062.0000000000, 57800000.0000000000], [211650062.0000000000, 58400000.0000000000], [211654877.2733277977, 58498017.1403295621], [211669276.7195967734, 58595090.3220161274], [211693121.6642677784, 58690284.6772544608], [211726182.4674887061, 58782683.4323650897], [211768140.7356516421, 58871396.7368259951], [211818592.3876974583, 58955570.2330196053], [211877051.5466372669, 59034393.2841636464], [211942955.2188134491, 59107106.7811865509], [212015668.7158363461, 59173010.4533627406], [212094491.7669804096, 59231469.6123025417], [212178665.2631739974, 59281921.2643483579], [212267378.5676349103, 59323879.5325112864], [212359777.3227455318, 59356940.3357322067], [212454971.6779838800, 59380785.2804032341], [212552044.8596704304, 59395184.7266721949], [212650062.0000000000, 59400000.0000000000], [218249492.3310896456, 59400000.0000000000], [218225470.2796331644, 107875914.0057790428], [134075355.1404574513, 107900333.6632250249], [134100156.1915711164, 38033177.9520300925], [218250062.0000000000, 38008758.2351448461]]);
}
}
translate(v = [0, 0, 1400000]) {
linear_extrude(convexity = 10, height = 3000000) {
polygon(points = [[219300062.0000000000, 37008468.0000000000], [219300062.0000000000, 49150000.0000000000], [219299821.2363336086, 49154900.8570164815], [219299101.2640201449, 49159754.5161008090], [219297909.0167866051, 49164514.2338627204], [219296255.9766255617, 49169134.1716182530], [219294158.0632174015, 49173569.8368413001], [219291635.4806151390, 49177778.5116509795], [219288712.5226681232, 49181719.6642081812], [219285417.3390593231, 49185355.3390593305], [219281781.6642081738, 49188650.5226681381], [219277840.5116509795, 49191573.4806151241], [219273631.8368412852, 49194096.0632174164], [219269196.1716182530, 49196193.9766255617], [219264576.2338627279, 49197847.0167866126], [219259816.5161008239, 49199039.2640201598], [219254962.8570164740, 49199759.2363336086], [219250062.0000000000, 49200000.0000000000], [212675062.0000000000, 49200000.0000000000], [212675062.0000000000, 49700000.0000000000], [213075062.0000000000, 49700000.0000000000], [213079962.8570164740, 49700240.7636663914], [213084816.5161007941, 49700960.7359798402], [213089576.2338627279, 49702152.9832133874], [213094196.1716182530, 49703806.0233744383], [213098631.8368412852, 49705903.9367825836], [213102840.5116509795, 49708426.5193848759], [213106781.6642081738, 49711349.4773318619], [213110417.3390593231, 49714644.6609406695], [213113712.5226681232, 49718280.3357918188], [213116635.4806151390, 49722221.4883490205], [213119158.0632174313, 49726430.1631586999], [213121255.9766255617, 49730865.8283817470], [213122909.0167866051, 49735485.7661372796], [213124101.2640201747, 49740245.4838991910], [213124821.2363336086, 49745099.1429835185], [213125062.0000000000, 49750000.0000000000], [213125062.0000000000, 57800000.0000000000], [213124821.2363336086, 57804900.8570164815], [213124101.2640201747, 57809754.5161008090], [213122909.0167866051, 57814514.2338627204], [213121255.9766255617, 57819134.1716182530], [213119158.0632174313, 57823569.8368413001], [213116635.4806151390, 57827778.5116509795], [213113712.5226681232, 57831719.6642081812], [213110417.3390593231, 57835355.3390593305], [213106781.6642081738, 57838650.5226681381], [213102840.5116509795, 57841573.4806151241], [213098631.8368412852, 57844096.0632174164], [213094196.1716182530, 57846193.9766255617], [213089576.2338627279, 57847847.0167866126], [213084816.5161007941, 57849039.2640201598], [213079962.8570164740, 57849759.2363336086], [213075062.0000000000, 57850000.0000000000], [212700062.0000000000, 57850000.0000000000], [212700062.0000000000, 58350000.0000000000], [219249988.0000000000, 58350000.0000000000], [219254890.3981388211, 58350240.9154777899], [219259745.5537340045, 58350961.3403004557], [219264506.6795004308, 58352154.3320083842], [219269127.8942824900, 58353808.3941948861], [219273564.6651935279, 58355907.5872926787], [219277774.2367621660, 58358431.6821774468], [219281716.0429505706, 58361356.3551081717], [219285352.0980739594, 58364653.4221258014], [219288647.3628539443, 58368291.1106512994], [219291570.0820789933, 58372234.3656658977], [219294092.0906172395, 58376445.1875229701], [219296189.0848329067, 58380882.9981361181], [219297840.8567914963, 58385505.0320147872], [219299031.4889957905, 58390266.7483789697], [219299749.5077767372, 58395122.2603817284], [219299987.9938608408, 58400024.7773033231], [219274974.9938608408, 108875648.7773033231], [219274732.0434487462, 108880547.0709154904], [219274010.3843984306, 108885398.0000459552], [219272816.9597353935, 108890154.8942835182], [219271163.2513052821, 108894771.9879193306], [219269065.1693081260, 108899204.8602553010], [219266542.8992273211, 108903410.8629720360], [219263620.7076268196, 108907349.5304451138], [219260326.7086842656, 108910982.9690616578], [219256692.5937068760, 108914276.2217918485], [219252753.3262316883, 108917197.6045077294], [219248546.8056441844, 108919719.0108138472], [219244113.5025511384, 108921816.1824565530], [219239496.0694162250, 108923468.9427108914], [219234738.9302038550, 108924661.3904992342], [219229887.8529797792, 108925382.0533742905], [219224989.5095799267, 108925623.9978947192], [133075014.5095799267, 108950623.9978947192], [133070111.5764135122, 108950384.4593511671], [133065255.7600877881, 108949665.3069619238], [133060493.8631598949, 108948473.4722434431], [133055871.7829492390, 108946820.4426385760], [133051434.0691584647, 108944722.1507953852], [133047223.4944830835, 108942198.8210010380], [133043280.6423489153, 108939274.7742510140], [133039643.5157507360, 108935978.1938323081], [133036347.1709620953, 108932340.8536801338], [133033423.3796471059, 108928397.8121263981], [133030900.3226307631, 108924187.0739915371], [133028802.3182793856, 108919749.2242769003], [133027149.5881091654, 108915127.0369881094], [133025958.0618821084, 108910365.0628598481], [133025239.2240675986, 108905509.1999556273], [133025000.0031501651, 108900606.2512815297], [133050511.0031501651, 37033450.2512815222], [133050753.3081911802, 37028551.4864123166], [133051474.4137663543, 37023700.03140Line truncated
}
}
translate(v = [176162531, 125454546, 2850000]) {
union() {
cylinder(d = 4500000, h = 300000);
translate(v = [0, 0, -2250000.0]) {
cylinder(d1 = 0, d2 = 4500000, h = 2250000.0);
}
translate(v = [0, 0, -10000000]) {
cylinder(d = 2000000, h = 10000000);
}
}
}
translate(v = [176162531, 20454546, 2850000]) {
union() {
cylinder(d = 4500000, h = 300000);
translate(v = [0, 0, -2250000.0]) {
cylinder(d1 = 0, d2 = 4500000, h = 2250000.0);
}
translate(v = [0, 0, -10000000]) {
cylinder(d = 2000000, h = 10000000);
}
}
}
translate(v = [228662531, 72954546, 2850000]) {
union() {
cylinder(d = 4500000, h = 300000);
translate(v = [0, 0, -2250000.0]) {
cylinder(d1 = 0, d2 = 4500000, h = 2250000.0);
}
translate(v = [0, 0, -10000000]) {
cylinder(d = 2000000, h = 10000000);
}
}
}
translate(v = [123662531, 72954546, 2850000]) {
union() {
cylinder(d = 4500000, h = 300000);
translate(v = [0, 0, -2250000.0]) {
cylinder(d1 = 0, d2 = 4500000, h = 2250000.0);
}
translate(v = [0, 0, -10000000]) {
cylinder(d = 2000000, h = 10000000);
}
}
}
}
}
}
/***********************************************
********* SolidPython code: **********
************************************************
from pcbnewTransition import pcbnew
import numpy as np
import json
from collections import OrderedDict
from kikit.common import *
from kikit.defs import *
from kikit.substrate import Substrate, extractRings, toShapely, linestringToKicad
from kikit.export import gerberImpl, pasteDxfExport
from kikit.export import exportSettingsJlcpcb
# Allow importing solid from this dir
import os
import sys
cur_path = os.path.abspath(os.path.dirname(__file__))
if cur_path not in sys.path:
sys.path.insert(0, cur_path)
import solid
import solid.utils
import subprocess
import shutil
from kikit.common import removeComponents, parseReferences
from shapely.geometry import Point
OUTER_BORDER = fromMm(7.5)
INNER_BORDER = fromMm(5)
MOUNTING_HOLES_COUNT = 3
MOUNTING_HOLE_R = fromMm(1)
HOLE_SPACING = fromMm(20)
def addBottomCounterpart(board, item):
item = item.Duplicate()
item.SetLayer(Layer.B_Paste)
board.Add(item)
def addRoundedCorner(board, center, start, end, thickness):
corner = pcbnew.PCB_SHAPE()
corner.SetShape(STROKE_T.S_ARC)
corner.SetCenter(toKiCADPoint((center[0], center[1])))
corner.SetStart(toKiCADPoint((start[0], start[1])))
if np.cross(start - center, end - center) > 0:
corner.SetArcAngleAndEnd(fromDegrees(90), True)
else:
corner.SetArcAngleAndEnd(fromDegrees(-90), True)
corner.SetWidth(thickness)
corner.SetLayer(Layer.F_Paste)
board.Add(corner)
addBottomCounterpart(board, corner)
def addLine(board, start, end, thickness):
line = pcbnew.PCB_SHAPE()
line.SetShape(STROKE_T.S_SEGMENT)
line.SetStart(toKiCADPoint((start[0], start[1])))
line.SetEnd(toKiCADPoint((end[0], end[1])))
line.SetWidth(thickness)
line.SetLayer(Layer.F_Paste)
board.Add(line)
addBottomCounterpart(board, line)
def addBite(board, origin, direction, normal, thickness):
"""
Adds a bite to the stencil, direction points to the bridge, normal points
inside the stencil
"""
direction = normalize(direction) * thickness
normal = normalize(normal) * thickness
center = toKiCADPoint((origin[0], origin[1])) + toKiCADPoint((normal[0], normal[1]))
start = origin
end = center + toKiCADPoint((direction[0], direction[1]))
# addLine(board, end, end + normal / 2, thickness)
addRoundedCorner(board, center, start, end, thickness)
def numberOfCuts(length, bridgeWidth, bridgeSpacing):
"""
Return number of bridges which fit inside the length and cut length
"""
count = round(np.floor((length + bridgeWidth) / (bridgeWidth + bridgeSpacing)))
cutLength = round((length - (count - 1) * bridgeWidth) / count)
return count, cutLength
def addFrame(board, rect, bridgeWidth, bridgeSpacing, clearance):
"""
Add rectangular frame to the board
"""
R=fromMm(1)
corners = [
(tl(rect), toKiCADPoint((R, 0)), toKiCADPoint((0, R))), # TL
(tr(rect), toKiCADPoint((0, R)), toKiCADPoint((-R, 0))), # TR
(br(rect), toKiCADPoint((-R, 0)), toKiCADPoint((0, -R))), # BR
(bl(rect), toKiCADPoint((0, -R)), toKiCADPoint((R, 0))) # BL
]
for c, sOffset, eOffset in corners:
addRoundedCorner(board, c + sOffset + eOffset, c + sOffset, c + eOffset, clearance)
count, cutLength = numberOfCuts(rect.GetWidth() - 2 * R, bridgeWidth, bridgeSpacing)
for i in range(count):
start = round(rect.GetX() + R + i * bridgeWidth + i * cutLength)
end = start + cutLength
y1, y2 = rect.GetY(), rect.GetY() + rect.GetHeight()
addLine(board, toKiCADPoint((start, y1)), toKiCADPoint((end, y1)), clearance)
if i != 0:
addBite(board, toKiCADPoint((start, y1)), toKiCADPoint((-1, 0)), toKiCADPoint((0, 1)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((end, y1)), toKiCADPoint((1, 0)), toKiCADPoint((0, 1)), clearance)
addLine(board, toKiCADPoint((start, y2)), toKiCADPoint((end, y2)), clearance)
if i != 0:
addBite(board, toKiCADPoint((start, y2)), toKiCADPoint((-1, 0)), toKiCADPoint((0, -1)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((end, y2)), toKiCADPoint((1, 0)), toKiCADPoint((0, -1)), clearance)
count, cutLength = numberOfCuts(rect.GetHeight() - 2 * R, bridgeWidth, bridgeSpacing)
for i in range(count):
start = rect.GetY() + R + i * bridgeWidth + i * cutLength
end = start + cutLength
x1, x2 = rect.GetX(), rect.GetX() + rect.GetWidth()
addLine(board, toKiCADPoint((x1, start)), toKiCADPoint((x1, end)), clearance)
if i != 0:
addBite(board, toKiCADPoint((x1, start)), toKiCADPoint((0, -1)), toKiCADPoint((1, 0)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((x1, end)), toKiCADPoint((0, 1)), toKiCADPoint((1, 0)), clearance)
addLine(board, toKiCADPoint((x2, start)), toKiCADPoint((x2, end)), clearance)
if i != 0:
addBite(board, toKiCADPoint((x2, start)), toKiCADPoint((0, -1)), toKiCADPoint((-1, 0)), clearance)
if i != count - 1:
addBite(board, toKiCADPoint((x2, end)), toKiCADPoint((0, 1)), toKiCADPoint((-1, 0)), clearance)
def addHole(board, position, radius):
circle = pcbnew.PCB_SHAPE()
circle.SetShape(STROKE_T.S_CIRCLE)
circle.SetCenter(toKiCADPoint((position[0], position[1])))
# Set 3'oclock point of the circle to set radius
circle.SetEnd(toKiCADPoint((position[0], position[1])) + toKiCADPoint((radius/2, 0)))
circle.SetWidth(radius)
circle.SetLayer(Layer.F_Paste)
board.Add(circle)
addBottomCounterpart(board, circle)
def addJigFrame(board, jigFrameSize, bridgeWidth=fromMm(2),
bridgeSpacing=fromMm(10), clearance=fromMm(0.5)):
"""
Given a Pcbnew board finds the board outline and creates a stencil for
KiKit's stencil jig.
Mainly, adds mounting holes and mouse bites to define the panel outline.
jigFrameSize is a tuple (width, height).
"""
bBox = findBoardBoundingBox(board)
frameSize = rectByCenter(rectCenter(bBox),
jigFrameSize[0] + 2 * (OUTER_BORDER + INNER_BORDER),
jigFrameSize[1] + 2 * (OUTER_BORDER + INNER_BORDER))
cutSize = rectByCenter(rectCenter(bBox),
jigFrameSize[0] + 2 * (OUTER_BORDER + INNER_BORDER) - fromMm(1),
jigFrameSize[1] + 2 * (OUTER_BORDER + INNER_BORDER) - fromMm(1))
addFrame(board, cutSize, bridgeWidth, bridgeSpacing, clearance)
for i in range(MOUNTING_HOLES_COUNT):
x = frameSize.GetX() + OUTER_BORDER / 2 + (i + 1) * (frameSize.GetWidth() - OUTER_BORDER) / (MOUNTING_HOLES_COUNT + 1)
addHole(board, toKiCADPoint((x, OUTER_BORDER / 2 + frameSize.GetY())), MOUNTING_HOLE_R)
addHole(board, toKiCADPoint((x, - OUTER_BORDER / 2 +frameSize.GetY() + frameSize.GetHeight())), MOUNTING_HOLE_R)
for i in range(MOUNTING_HOLES_COUNT):
y = frameSize.GetY() + OUTER_BORDER / 2 + (i + 1) * (frameSize.GetHeight() - OUTER_BORDER) / (MOUNTING_HOLES_COUNT + 1)
addHole(board, toKiCADPoint((OUTER_BORDER / 2 + frameSize.GetX(), y)), MOUNTING_HOLE_R)
addHole(board, toKiCADPoint((- OUTER_BORDER / 2 +frameSize.GetX() + frameSize.GetWidth(), y)), MOUNTING_HOLE_R)
PIN_TOLERANCE = fromMm(0.05)
addHole(board, tl(frameSize) + toKiCADPoint((OUTER_BORDER / 2, OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, tr(frameSize) + toKiCADPoint((-OUTER_BORDER / 2, OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, br(frameSize) + toKiCADPoint((-OUTER_BORDER / 2, -OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
addHole(board, bl(frameSize) + toKiCADPoint((OUTER_BORDER / 2, -OUTER_BORDER / 2)), MOUNTING_HOLE_R + PIN_TOLERANCE)
def jigMountingHoles(jigFrameSize, origin=toKiCADPoint((0, 0))):
""" Get list of all mounting holes in a jig of given size """
w, h = jigFrameSize
holes = [
toKiCADPoint((0, (w + INNER_BORDER) / 2)),
toKiCADPoint((0, -(w + INNER_BORDER) / 2)),
toKiCADPoint(((h + INNER_BORDER) / 2, 0)),
toKiCADPoint((-(h + INNER_BORDER) / 2, 0)),
]
return [x + origin for x in holes]
def createOuterPolygon(board, jigFrameSize, outerBorder):
bBox = findBoardBoundingBox(board)
centerpoint = rectCenter(bBox)
holes = jigMountingHoles(jigFrameSize, centerpoint)
outerSubstrate = Substrate(collectEdges(board, Layer.Edge_Cuts))
outerSubstrate.substrates = outerSubstrate.substrates.buffer(outerBorder)
tabs = []
for hole in holes:
tab, _ = outerSubstrate.tab(hole, centerpoint - hole, INNER_BORDER, maxHeight=fromMm(1000))
tabs.append(tab)
outerSubstrate.union(tabs)
outerSubstrate.union([Point(x).buffer(INNER_BORDER / 2) for x in holes])
outerSubstrate.millFillets(fromMm(3))
return outerSubstrate.exterior(), holes
def createOffsetPolygon(board, offset):
outerSubstrate = Substrate(collectEdges(board, Layer.Edge_Cuts))
outerSubstrate.substrates = outerSubstrate.substrates.buffer(offset)
return outerSubstrate.exterior()
def m2countersink():
HEAD_DIA = fromMm(4.5)
HOLE_LEN = fromMm(10)
SINK_EXTRA = fromMm(0.3)
sinkH = np.sqrt(HEAD_DIA**2 / 4)
sink = solid.cylinder(d1=0, d2=HEAD_DIA, h=sinkH)
sinkE = solid.cylinder(d=HEAD_DIA, h=SINK_EXTRA)
hole = solid.cylinder(h=HOLE_LEN, d=fromMm(2))
return sinkE + solid.utils.down(sinkH)(sink) + solid.utils.down(HOLE_LEN)(hole)
def mirrorX(linestring, origin):
return [(2 * origin - x, y) for x, y in linestring]
def makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, topSide):
bBox = findBoardBoundingBox(board)
centerpoint = rectCenter(bBox)
top = jigThickness - fromMm(0.15)
pcbBottom = jigThickness - pcbThickness
outerPolygon, holes = createOuterPolygon(board, jigFrameSize, outerBorder)
outerRing = outerPolygon.exterior.coords
if topSide:
outerRing = mirrorX(outerRing, centerpoint[0])
body = solid.linear_extrude(height=top, convexity=10)(solid.polygon(
outerRing))
innerRings = [x.exterior.coords for x in listGeometries(createOffsetPolygon(board, - innerBorder))]
if topSide:
innerRings = [mirrorX(innerRing, centerpoint[0]) for innerRing in innerRings]
innerCutout = solid.utils.down(jigThickness)(
solid.linear_extrude(height=3 * jigThickness, convexity=10)(solid.polygon(innerRings[0])))
for innerRing in innerRings[1:]:
innerCutout = innerCutout + solid.utils.down(jigThickness)(
solid.linear_extrude(height=3 * jigThickness, convexity=10)(solid.polygon(innerRing)))
registerRing = createOffsetPolygon(board, tolerance).exterior.coords
if topSide:
registerRing = mirrorX(registerRing, centerpoint[0])
registerCutout = solid.utils.up(jigThickness - pcbThickness)(
solid.linear_extrude(height=jigThickness, convexity=10)(solid.polygon(registerRing)))
register = body - innerCutout - registerCutout
for hole in holes:
register = register - solid.translate([hole[0], hole[1], top])(m2countersink())
return solid.scale(toMm(1))(
solid.translate([-centerpoint[0], -centerpoint[1], 0])(register))
def makeTopRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder=fromMm(3), innerBorder=fromMm(1),
tolerance=fromMm(0.05)):
"""
Create a SolidPython representation of the top register
"""
return makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, True)
def makeBottomRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder=fromMm(3), innerBorder=fromMm(1),
tolerance=fromMm(0.05)):
"""
Create a SolidPython representation of the top register
"""
return makeRegister(board, jigFrameSize, jigThickness, pcbThickness,
outerBorder, innerBorder, tolerance, False)
def renderScad(infile, outfile):
infile = os.path.abspath(infile)
outfile = os.path.abspath(outfile)
try:
subprocess.run(["openscad", "-o", outfile, infile],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as e:
message = f"Cannot render {outfile}, OpenSCAD error:\n"
message += (e.stdout.decode("utf-8") + "\n") if e.stdout is not None else ""
message += (e.stderr.decode("utf-8") + "\n") if e.stderr is not None else ""
raise RuntimeError(message)
except FileNotFoundError as e:
message = f"OpenSCAD is not available.\n"
message += f"Did you install it? Program `openscad` has to be in PATH"
raise RuntimeError(message)
def shapelyToSHAPE_POLY_SET(polygon):
p = pcbnew.SHAPE_POLY_SET()
p.AddOutline(linestringToKicad(polygon.exterior))
return p
def cutoutComponents(board, components):
topCutout = extractComponentPolygons(components, pcbnew.F_CrtYd)
for polygon in topCutout:
zone = pcbnew.PCB_SHAPE()
zone.SetShape(STROKE_T.S_POLYGON)
zone.SetPolyShape(shapelyToSHAPE_POLY_SET(polygon))
zone.SetLayer(Layer.F_Paste)
board.Add(zone)
bottomCutout = extractComponentPolygons(components, pcbnew.B_CrtYd)
for polygon in bottomCutout:
zone = pcbnew.PCB_SHAPE()
zone.SetShape(STROKE_T.S_POLYGON)
zone.SetPolyShape(shapelyToSHAPE_POLY_SET(polygon))
zone.SetLayer(Layer.B_Paste)
board.Add(zone)
def setStencilLayerVisibility(boardName):
prlPath = os.path.splitext(boardName)[0] + ".kicad_prl"
try:
with open(prlPath, encoding="utf-8") as f:
# We use ordered dict, so we preserve the ordering of the keys and
# thus, formatting
prl = json.load(f, object_pairs_hook=OrderedDict)
except FileNotFoundError:
# KiCAD didn't generate project local settings, let's create an empty one
prl = {
"board": {}
}
prl["board"]["visible_layers"] = "ffc000c_7ffffffe"
prl["board"]["visible_items"] = [
1,
2,
3,
4,
9,
10,
12,
13,
21,
22,
24,
25,
26,
27,
28,
29,
30,
34,
35
]
with open(prlPath, "w", encoding="utf-8") as f:
json.dump(prl, f, indent=2)
pass
from pathlib import Path
import os
def create(inputboard, outputdir, jigsize, jigthickness, pcbthickness,
registerborder, tolerance, ignore, cutout):
board = pcbnew.LoadBoard(inputboard)
refs = parseReferences(ignore)
removeComponents(board, refs)
Path(outputdir).mkdir(parents=True, exist_ok=True)
jigsize = (fromMm(jigsize[0]), fromMm(jigsize[1]))
addJigFrame(board, jigsize)
cutoutComponents(board, getComponents(board, parseReferences(cutout)))
stencilFile = os.path.join(outputdir, "stencil.kicad_pcb")
board.Save(stencilFile)
setStencilLayerVisibility(stencilFile)
plotPlan = [
# name, id, comment
("PasteBottom", pcbnew.B_Paste, "Paste Bottom"),
("PasteTop", pcbnew.F_Paste, "Paste top"),
]
# get a copy of exportSettingsJlcpcb dictionary and
# exclude the Edge.Cuts layer for creation of stencil gerber files
exportSettings = exportSettingsJlcpcb.copy()
exportSettings["ExcludeEdgeLayer"] = True
gerberDir = os.path.join(outputdir, "gerber")
gerberImpl(stencilFile, gerberDir, plotPlan, False, exportSettings)
shutil.make_archive(os.path.join(outputdir, "gerbers"), "zip", gerberDir)
jigthickness = fromMm(jigthickness)
pcbthickness = fromMm(pcbthickness)
outerBorder, innerBorder = fromMm(registerborder[0]), fromMm(registerborder[1])
tolerance = fromMm(tolerance)
topRegister = makeTopRegister(board, jigsize,jigthickness, pcbthickness,
outerBorder, innerBorder, tolerance)
bottomRegister = makeBottomRegister(board, jigsize,jigthickness, pcbthickness,
outerBorder, innerBorder, tolerance)
topRegisterFile = os.path.join(outputdir, "topRegister.scad")
solid.scad_render_to_file(topRegister, topRegisterFile)
renderScad(topRegisterFile, os.path.join(outputdir, "topRegister.stl"))
bottomRegisterFile = os.path.join(outputdir, "bottomRegister.scad")
solid.scad_render_to_file(bottomRegister, bottomRegisterFile)
renderScad(bottomRegisterFile, os.path.join(outputdir, "bottomRegister.stl"))
def printedStencilSubstrate(outlineDxf, thickness, frameHeight, frameWidth, frameClearance):
bodyOffset = solid.utils.up(0) if frameWidth + frameClearance == 0 else solid.offset(r=frameWidth + frameClearance)
body = solid.linear_extrude(height=thickness + frameHeight)(
bodyOffset(solid.import_dxf(outlineDxf)))
boardOffset = solid.utils.up(0) if frameClearance == 0 else solid.offset(r=frameClearance)
board = solid.utils.up(thickness)(
solid.linear_extrude(height=thickness + frameHeight)(
boardOffset(solid.import_dxf(outlineDxf))))
return body - board
def getComponents(board, references):
"""
Return a list of components based on designator
"""
return [f for f in board.GetFootprints() if f.GetReference() in references]
def collectFootprintEdges(footprint, layer):
"""
Return all edges on given layer in given footprint
"""
return [e for e in footprint.GraphicalItems() if e.GetLayer() == layer]
def extractComponentPolygons(footprints, srcLayer):
"""
Return a list of shapely polygons with holes for already placed components.
The source layer defines the geometry on which the cutout is computed.
Usually it a font or back courtyard
"""
polygons = []
for f in footprints:
edges = collectFootprintEdges(f, srcLayer)
for ring in extractRings(edges):
polygons.append(toShapely(ring, edges))
return polygons
def printedStencil(outlineDxf, holesDxf, extraHoles, thickness, frameHeight, frameWidth,
frameClearance, enlargeHoles, front):
zScale = -1 if front else 1
xRotate = 180 if front else 0
substrate = solid.scale([1, 1, zScale])(printedStencilSubstrate(outlineDxf,
thickness, frameHeight, frameWidth, frameClearance))
holesOffset = solid.utils.up(0) if enlargeHoles == 0 else solid.offset(delta=enlargeHoles)
holes = solid.linear_extrude(height=4*thickness, center=True)(
holesOffset(solid.import_dxf(holesDxf)))
substrate -= holes
for h in extraHoles:
substrate -= solid.scale([toMm(1), -toMm(1), 1])(
solid.linear_extrude(height=4*thickness, center=True)(
solid.polygon(h.exterior.coords)))
return solid.rotate(a=xRotate, v=[1, 0, 0])(substrate)
def createPrinted(inputboard, outputdir, pcbthickness, thickness, framewidth,
ignore, cutout, frameclearance, enlargeholes):
"""
Create a 3D printed self-registering stencil.
"""
board = pcbnew.LoadBoard(inputboard)
refs = parseReferences(ignore)
cutoutComponents = getComponents(board, parseReferences(cutout))
removeComponents(board, refs)
Path(outputdir).mkdir(parents=True, exist_ok=True)
# We create the stencil based on DXF export. Using it avoids the necessity
# to interpret KiCAD PAD shapes which constantly change with newer and newer
# versions.
height = min(pcbthickness, max(0.5, pcbthickness - 0.3))
bottomPaste, topPaste, outline = pasteDxfExport(board, outputdir)
# On Windows, OpenSCAD requires to use forward slashes instead of backslashes,
# hence, the replacement:
if os.name == "nt":
bottomPaste = bottomPaste.replace("\\", "/")
topPaste = topPaste.replace("\\", "/")
outline = outline.replace("\\", "/")
topCutout = extractComponentPolygons(cutoutComponents, pcbnew.F_CrtYd)
bottomCutout = extractComponentPolygons(cutoutComponents, pcbnew.B_CrtYd)
topStencil = printedStencil(outline, topPaste, topCutout, thickness, height,
framewidth, frameclearance, enlargeholes, True)
bottomStencil = printedStencil(outline, bottomPaste, bottomCutout, thickness,
height, framewidth, frameclearance, enlargeholes, False)
bottomStencilFile = os.path.join(outputdir, "bottomStencil.scad")
solid.scad_render_to_file(bottomStencil, bottomStencilFile,
file_header=f'$fa = 0.4; $fs = 0.4;', include_orig_code=True)
renderScad(bottomStencilFile, os.path.join(outputdir, "bottomStencil.stl"))
topStencilFile = os.path.join(outputdir, "topStencil.scad")
solid.scad_render_to_file(topStencil, topStencilFile,
file_header=f'$fa = 0.4; $fs = 0.4;', include_orig_code=True)
renderScad(topStencilFile, os.path.join(outputdir, "topStencil.stl"))
************************************************/
File diff suppressed because it is too large. Load diff