2022-03-05 21:39:17 +01:00
|
|
|
// Generated by SolidPython 1.1.3 on 2022-03-05 21:26:43
|
2020-11-05 19:04:30 +01:00
|
|
|
|
|
|
|
|
|
|
|
scale(v = 0.0000010000) {
|
|
|
|
translate(v = [-63500109, -68500031, 0]) {
|
|
|
|
difference() {
|
|
|
|
linear_extrude(convexity = 10, height = 2850000) {
|
2022-03-05 21:39:17 +01:00
|
|
|
polygon(points = [[28500347.8510667309, 68499662.9277706146], [28500341.8278942220, 68499908.2846171707], [28500341.8278942220, 68500153.7153828889], [28500347.8510667309, 68500399.0722294450], [28501006.9684883878, 68518294.2227162421], [28501012.2377722189, 68518416.8272300959], [28511243.9455472752, 68726688.0235936195], [28511250.7189803272, 68726810.5542133749], [28512348.8068657070, 68744684.1394643486], [28512366.8618728817, 68744928.9052247703], [28512390.9182946831, 68745173.1541742682], [28512420.9616404288, 68745416.7391863912], [28514830.9367080182, 68763161.1148274690], [28514848.1979626492, 68763282.6124869585], [28545444.7843487896, 68969548.0433772951], [28545463.5352668576, 68969669.3200660795], [28548308.2532725967, 68987349.2076839656], [28548350.2125798650, 68987591.0251301825], [28548398.0937469825, 68987831.7400125414], [28548451.8679321222, 68988071.2073334306], [28552589.4912684485, 69005493.9200919122], [28552618.5782585591, 69005613.1408081353], [28603285.3814028688, 69207886.3573684394], [28603315.9292243645, 69208005.2121654600], [28607879.8811611533, 69225321.1351685822], [28607945.3406774476, 69225557.6754663885], [28608016.5854680613, 69225792.5380657315], [28608093.5726177953, 69226025.5814941525], [28613918.9966479391, 69242958.8411206454], [28613959.6292499155, 69243074.6367329508], [28684208.7001412548, 69439407.6373156607], [28684250.7506739609, 69439522.9255842268], [28690489.9831895977, 69456308.1221675724], [28690578.3125039898, 69456537.1073046029], [28690672.2347918227, 69456763.8557657003], [28690771.6934778169, 69456988.2309660614], [28698228.8161840588, 69473268.9609144777], [28698280.6030837297, 69473380.2162477970], [28787435.4047670066, 69661882.2067295760], [28787488.5530413017, 69661992.8181806654], [28795342.9789159670, 69678085.6377255321], [28795453.3273688778, 69678304.8624497205], [28795569.0226309523, 69678521.3130609393], [28795689.9950116873, 69678734.8591773957], [28804707.0002261549, 69694206.2671188712], [28804769.4426873699, 69694311.9107235521], [28911971.3656853661, 69873167.5138904154], [28912035.0998543166, 69873272.3832752705], [28921429.0766732581, 69888517.8431323618], [28921560.3815487623, 69888725.1961897910], [28921696.7355764620, 69888929.2644134015], [28921838.0566217862, 69889129.9248801321], [28932328.1056545600, 69903643.0126977414], [28932400.6023222804, 69903742.0271681100], [29056617.2335141413, 70071228.7657894939], [29056690.9397828579, 70071326.8831586093], [29067533.9984141327, 70085578.1612155586], [29067684.9951745011, 70085771.6456829458], [29067840.6948039979, 70085961.3662304133], [29068001.0035149865, 70086147.2085774243], [29079863.0714600682, 70099562.2073026896], [29079944.9241517521, 70099653.6390752494], [29219979.9894755818, 70254158.5243008286], [29220062.9580124058, 70254248.9447303116], [29232250.6738731265, 70267368.7933879197], [29232419.9083370119, 70267546.5459040552], [29232593.4540957622, 70267720.0916628093], [29232771.2066118978, 70267889.3261266947], [29245891.0552700385, 70280077.0419879258], [29245981.4756995253, 70280160.0105247498], [29400486.3609249517, 70420195.0758484304], [29400577.7926975191, 70420276.9285401106], [29413992.7914226465, 70432138.9964850694], [29414178.6337696500, 70432299.3051960617], [29414368.3543171249, 70432455.0048255622], [29414561.8387845084, 70432606.0015859306], [29428813.1168413870, 70443449.0602171421], [29428911.2342105024, 70443522.7664858550], [29596397.9728319012, 70567739.3976777345], [29596496.9873022661, 70567811.8943454623], [29611010.0751198791, 70578301.9433782250], [29611210.7355866060, 70578443.2644235492], [29611414.8038102165, 70578579.6184512377], [29611622.1568676494, 70578710.9233267456], [29626867.6167248525, 70588104.9001457691], [29626972.4861097075, 70588168.6343147159], [29805828.0892762356, 70695370.5573125035], [29805933.7328809127, 70695432.9997737259], [29821405.1408224031, 70704450.0049882084], [29821618.6869388707, 70704570.9773689359], [29821835.1375500746, 70704686.6726310104], [29822054.3622742705, 70704797.0210839212], [29838147.1818189286, 707
|
2020-11-05 19:04:30 +01:00
|
|
|
}
|
|
|
|
translate(v = [0, 0, -3000000]) {
|
|
|
|
linear_extrude(convexity = 10, height = 9000000) {
|
2022-03-05 21:39:17 +01:00
|
|
|
polygon(points = [[51000000.0000000000, 86000000.0000000000], [53790000.0000000000, 86000000.0000000000], [53882633.5577495545, 86004299.7318576872], [53974470.5165804178, 86017161.9520426840], [54064721.1278973818, 86038476.0523591787], [54118700.1777984276, 86053898.6380451918], [54182535.6250363365, 86069857.4998546690], [54214721.1278973818, 86078476.0523591787], [54284721.1278973818, 86098476.0523591787], [54403919.2985791713, 86140854.9699819386], [54473919.2985791713, 86170854.9699819386], [54576138.9383568317, 86221756.8578755409], [54646138.9383568317, 86261756.8578755409], [54720358.4621786699, 86308604.0999486446], [54790184.3996644765, 86361778.7204026282], [54910184.3996644765, 86461778.7204026282], [54977106.7811865509, 86522893.2188134491], [55038221.2795973793, 86589815.6003355235], [55138221.2795973793, 86709815.6003355235], [55191395.9000513554, 86779641.5378213227], [55238243.1421244591, 86853861.0616431683], [55278243.1421244591, 86923861.0616431683], [55329145.0300180614, 87026080.7014208287], [55359145.0300180614, 87096080.7014208287], [55401523.9476408213, 87215278.8721026182], [55421523.9476408213, 87285278.8721026182], [55430142.5001453310, 87317464.3749636710], [55446101.3619547933, 87381299.8222015202], [55461523.9476408213, 87435278.8721026182], [55482838.0479573235, 87525529.4834195822], [55495700.2681423053, 87617366.4422504455], [55500000.0000000000, 87710000.0000000000], [55500000.0000000000, 89290000.0000000000], [55495700.2681423053, 89382633.5577495545], [55482838.0479573235, 89474470.5165804178], [55461523.9476408213, 89564721.1278973818], [55446101.3619547933, 89618700.1777984798], [55430142.5001453310, 89682535.6250363290], [55421523.9476408213, 89714721.1278973818], [55401523.9476408213, 89784721.1278973818], [55359145.0300180614, 89903919.2985791713], [55329145.0300180614, 89973919.2985791713], [55278243.1421244591, 90076138.9383568317], [55238243.1421244591, 90146138.9383568317], [55191395.9000513554, 90220358.4621786773], [55138221.2795973793, 90290184.3996644765], [55038221.2795973793, 90410184.3996644765], [54977106.7811865509, 90477106.7811865509], [54910184.3996644765, 90538221.2795973718], [54790184.3996644765, 90638221.2795973718], [54720358.4621786699, 90691395.9000513554], [54646138.9383568317, 90738243.1421244591], [54576138.9383568317, 90778243.1421244591], [54473919.2985791713, 90829145.0300180614], [54333919.2985791713, 90889145.0300180614], [54246372.1112165526, 90921911.8286210746], [54156147.7913113385, 90946360.6568841338], [54064034.7345892116, 90962277.8767136633], [54058710.9124430045, 90962943.3544819355], [53974470.5165804178, 90982838.0479573160], [53882633.5577495545, 90995700.2681423128], [53790000.0000000000, 91000000.0000000000], [51000000.0000000000, 91000000.0000000000], [51000000.0000000000, 94000000.0000000000], [76000000.0000000000, 94000000.0000000000], [76000000.0000000000, 91000000.0000000000], [73210000.0000000000, 91000000.0000000000], [73117366.4422504455, 90995700.2681423128], [73025529.4834195822, 90982838.0479573160], [72941289.0875569880, 90962943.3544819355], [72935965.2654107958, 90962277.8767136633], [72843852.2086886615, 90946360.6568841338], [72753627.8887834549, 90921911.8286210746], [72666080.7014208287, 90889145.0300180614], [72526080.7014208287, 90829145.0300180614], [72423861.0616431683, 90778243.1421244591], [72353861.0616431683, 90738243.1421244591], [72279641.5378213227, 90691395.9000513554], [72209815.6003355235, 90638221.2795973718], [72089815.6003355235, 90538221.2795973718], [72022893.2188134491, 90477106.7811865509], [71961778.7204026282, 90410184.3996644765], [71861778.7204026282, 90290184.3996644765], [71808604.0999486446, 90220358.4621786773], [71761756.8578755409, 90146138.9383568317], [71721756.8578755409, 90076138.9383568317], [71670854.9699819386, 89973919.2985791713], [71640854.9699819386, 89903919.2985791713], [71598476.0523591787, 89784721.1278973818], [71578476.0523591787, 89714721.1278973818], [71569857.4998546690, 89682535.6250363290], [71553898.6380452067, 89618700.1777984798], [71538476.0523591787, 89
|
2020-11-05 19:04:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
translate(v = [0, 0, 1400000]) {
|
|
|
|
linear_extrude(convexity = 10, height = 3000000) {
|
2022-03-05 21:39:17 +01:00
|
|
|
polygon(points = [[49500000.0000000000, 72350000.0000000000], [49500000.0000000000, 86560000.0000000000], [49503746.2216585502, 86621091.6318478584], [49514928.7499273345, 86681267.8125181645], [49534928.7499273345, 86761267.8125181645], [49555761.6545573696, 86825695.3381770551], [49575761.6545573696, 86875695.3381770551], [49592786.4045000449, 86913606.7977499813], [49642786.4045000449, 87013606.7977499813], [49668832.2517301217, 87059476.7667486072], [49699565.5952784866, 87102347.5237772167], [49739565.5952784866, 87152347.5237772167], [49776446.6094067246, 87193553.3905932754], [49816446.6094067246, 87233553.3905932754], [49870000.0000000000, 87280000.0000000000], [49910000.0000000000, 87310000.0000000000], [49932649.9018873870, 87326025.1471689194], [49962649.9018873870, 87346025.1471689194], [50016393.2022500187, 87377213.5954999626], [50116393.2022500187, 87427213.5954999626], [50156911.6920240745, 87445272.6850810349], [50198871.3641460910, 87459669.3737794906], [50241941.9324309081, 87470290.3378454596], [50291941.9324309081, 87480290.3378454596], [50307800.5063473210, 87483196.9619160742], [50367800.5063473210, 87493196.9619160742], [50408759.7342275530, 87498296.3380148560], [50450000.0000000000, 87500000.0000000000], [53719972.5276798457, 87500000.0000000000], [53722639.4360513091, 87500761.9738204181], [53738732.1874818355, 87505071.2500726730], [53810649.9111007601, 87523050.6809774041], [53842168.3917083368, 87532055.9611510038], [53856757.6787826493, 87538308.5127542764], [53863718.1253838912, 87542285.9108121246], [53914988.6510951370, 87585011.3489048332], [53957714.0891878977, 87636281.8746161461], [53961691.4872457311, 87643242.3212173581], [53967944.0388490185, 87657831.6082916856], [53976949.3190226033, 87689350.0888992399], [53994928.7499273345, 87761267.8125181645], [53999238.0261795893, 87777360.5639486909], [54000000.0000000000, 87780027.4723201245], [54000000.0000000000, 89219972.5276798755], [53999238.0261795893, 89222639.4360513091], [53994928.7499273345, 89238732.1874818355], [53976949.3190226033, 89310649.9111007601], [53967944.0388490185, 89342168.3917083144], [53961691.4872457311, 89356757.6787826419], [53957714.0891878977, 89363718.1253838539], [53914988.6510951370, 89414988.6510951668], [53863718.1253838912, 89457714.0891878754], [53856757.6787826493, 89461691.4872457236], [53807929.9820934683, 89482617.6429696530], [53797982.6327053979, 89483861.0616431683], [53760014.9879916906, 89490098.9922711998], [53722639.4360513091, 89499238.0261795819], [53719972.5276798457, 89500000.0000000000], [50010000.0000000000, 89500000.0000000000], [49960991.4298352227, 89502407.6366638988], [49912454.8389919326, 89509607.3597983867], [49864857.6613727659, 89521529.8321338892], [49818658.2838174552, 89538060.2337443531], [49774301.6315869987, 89559039.3678258210], [49732214.8834901974, 89584265.1938487291], [49692803.3579181805, 89613494.7733186334], [49656446.6094067246, 89646446.6094067246], [49646446.6094067246, 89656446.6094067246], [49613494.7733186334, 89692803.3579181731], [49584265.1938487291, 89732214.8834902048], [49559039.3678258210, 89774301.6315869987], [49538060.2337443605, 89818658.2838174552], [49521529.8321338966, 89864857.6613727659], [49509607.3597983867, 89912454.8389919400], [49502407.6366638988, 89960991.4298352152], [49500000.0000000000, 90010000.0000000000], [49500000.0000000000, 95000000.0000000000], [49502407.6366638988, 95049008.5701647848], [49509607.3597983867, 95097545.1610080600], [49521529.8321338966, 95145142.3386272341], [49538060.2337443531, 95191341.7161825448], [49559039.3678258210, 95235698.3684130013], [49584265.1938487291, 95277785.1165097952], [49613494.7733186334, 95317196.6420818269], [49646446.6094067246, 95353553.3905932754], [49682803.3579181805, 95386505.2266813666], [49722214.8834901974, 95415734.8061512709], [49764301.6315869987, 95440960.6321741790], [49808658.2838174552, 95461939.7662556469], [49854857.6613727659, 95478470.1678661108], [49902454.8389919326, 95490392.6402016133], [49950991.4298352227, 95497592.3633361012], [50000000.0000000000, 95
|
2020-11-05 19:04:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
translate(v = [63500109, 101000031, 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 = [63500109, 36000031, 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 = [96000109, 68500031, 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 = [31000109, 68500031, 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: **********
|
|
|
|
************************************************
|
|
|
|
|
2022-03-05 21:39:17 +01:00
|
|
|
from pcbnewTransition import pcbnew
|
2020-11-05 19:04:30 +01:00
|
|
|
from pcbnew import wxPoint
|
|
|
|
import numpy as np
|
2022-03-05 21:39:17 +01:00
|
|
|
import json
|
|
|
|
from collections import OrderedDict
|
|
|
|
from pcbnewTransition.transition import isV6
|
2020-11-05 19:04:30 +01:00
|
|
|
from kikit.common import *
|
|
|
|
from kikit.defs import *
|
|
|
|
from kikit.substrate import Substrate, extractRings, toShapely, linestringToKicad
|
|
|
|
from kikit.export import gerberImpl, pasteDxfExport
|
2022-03-05 21:39:17 +01:00
|
|
|
from kikit.export import exportSettingsJlcpcb
|
2020-11-05 19:04:30 +01:00
|
|
|
import solid
|
|
|
|
import solid.utils
|
|
|
|
import subprocess
|
|
|
|
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(wxPoint(center[0], center[1]))
|
2022-03-05 21:39:17 +01:00
|
|
|
if isV6():
|
|
|
|
corner.SetStart(wxPoint(start[0], start[1]))
|
|
|
|
else:
|
|
|
|
corner.SetArcStart(wxPoint(start[0], start[1]))
|
|
|
|
|
2020-11-05 19:04:30 +01:00
|
|
|
if np.cross(start - center, end - center) > 0:
|
2022-03-05 21:39:17 +01:00
|
|
|
if isV6():
|
|
|
|
corner.SetArcAngleAndEnd(fromDegrees(90), True)
|
|
|
|
else:
|
|
|
|
corner.SetAngle(fromDegrees(90))
|
2020-11-05 19:04:30 +01:00
|
|
|
else:
|
2022-03-05 21:39:17 +01:00
|
|
|
if isV6():
|
|
|
|
corner.SetArcAngleAndEnd(fromDegrees(-90), True)
|
|
|
|
else:
|
|
|
|
corner.SetAngle(fromDegrees(-90))
|
2020-11-05 19:04:30 +01:00
|
|
|
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(wxPoint(start[0], start[1]))
|
|
|
|
line.SetEnd(wxPoint(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 = wxPoint(origin[0], origin[1]) + wxPoint(normal[0], normal[1])
|
|
|
|
start = origin
|
|
|
|
end = center + wxPoint(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 = int(np.floor((length + bridgeWidth) / (bridgeWidth + bridgeSpacing)))
|
|
|
|
cutLength = (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), wxPoint(R, 0), wxPoint(0, R)), # TL
|
|
|
|
(tr(rect), wxPoint(0, R), wxPoint(-R, 0)), # TR
|
|
|
|
(br(rect), wxPoint(-R, 0), wxPoint(0, -R)), # BR
|
|
|
|
(bl(rect), wxPoint(0, -R), wxPoint(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 = rect.GetX() + R + i * bridgeWidth + i * cutLength
|
|
|
|
end = start + cutLength
|
|
|
|
|
|
|
|
y1, y2 = rect.GetY(), rect.GetY() + rect.GetHeight()
|
|
|
|
addLine(board, wxPoint(start, y1), wxPoint(end, y1), clearance)
|
|
|
|
if i != 0:
|
|
|
|
addBite(board, wxPoint(start, y1), wxPoint(-1, 0), wxPoint(0, 1), clearance)
|
|
|
|
if i != count - 1:
|
|
|
|
addBite(board, wxPoint(end, y1), wxPoint(1, 0), wxPoint(0, 1), clearance)
|
|
|
|
addLine(board, wxPoint(start, y2), wxPoint(end, y2), clearance)
|
|
|
|
if i != 0:
|
|
|
|
addBite(board, wxPoint(start, y2), wxPoint(-1, 0), wxPoint(0, -1), clearance)
|
|
|
|
if i != count - 1:
|
|
|
|
addBite(board, wxPoint(end, y2), wxPoint(1, 0), wxPoint(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, wxPoint(x1, start), wxPoint(x1, end), clearance)
|
|
|
|
if i != 0:
|
|
|
|
addBite(board, wxPoint(x1, start), wxPoint(0, -1), wxPoint(1, 0), clearance)
|
|
|
|
if i != count - 1:
|
|
|
|
addBite(board, wxPoint(x1, end), wxPoint(0, 1), wxPoint(1, 0), clearance)
|
|
|
|
addLine(board, wxPoint(x2, start), wxPoint(x2, end), clearance)
|
|
|
|
if i != 0:
|
|
|
|
addBite(board, wxPoint(x2, start), wxPoint(0, -1), wxPoint(-1, 0), clearance)
|
|
|
|
if i != count - 1:
|
|
|
|
addBite(board, wxPoint(x2, end), wxPoint(0, 1), wxPoint(-1, 0), clearance)
|
|
|
|
|
|
|
|
def addHole(board, position, radius):
|
|
|
|
circle = pcbnew.PCB_SHAPE()
|
|
|
|
circle.SetShape(STROKE_T.S_CIRCLE)
|
|
|
|
circle.SetCenter(wxPoint(position[0], position[1]))
|
2022-03-05 21:39:17 +01:00
|
|
|
if isV6():
|
|
|
|
# Set 3'oclock point of the circle to set radius
|
|
|
|
circle.SetEnd(wxPoint(position[0], position[1]) + wxPoint(radius/2, 0))
|
|
|
|
else:
|
|
|
|
circle.SetArcStart(wxPoint(position[0], position[1]) + wxPoint(radius/2, 0))
|
2020-11-05 19:04:30 +01:00
|
|
|
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, wxPoint(x, OUTER_BORDER / 2 + frameSize.GetY()), MOUNTING_HOLE_R)
|
|
|
|
addHole(board, wxPoint(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, wxPoint(OUTER_BORDER / 2 + frameSize.GetX(), y), MOUNTING_HOLE_R)
|
|
|
|
addHole(board, wxPoint(- OUTER_BORDER / 2 +frameSize.GetX() + frameSize.GetWidth(), y), MOUNTING_HOLE_R)
|
|
|
|
|
|
|
|
PIN_TOLERANCE = fromMm(0.05)
|
|
|
|
addHole(board, tl(frameSize) + wxPoint(OUTER_BORDER / 2, OUTER_BORDER / 2), MOUNTING_HOLE_R + PIN_TOLERANCE)
|
|
|
|
addHole(board, tr(frameSize) + wxPoint(-OUTER_BORDER / 2, OUTER_BORDER / 2), MOUNTING_HOLE_R + PIN_TOLERANCE)
|
|
|
|
addHole(board, br(frameSize) + wxPoint(-OUTER_BORDER / 2, -OUTER_BORDER / 2), MOUNTING_HOLE_R + PIN_TOLERANCE)
|
|
|
|
addHole(board, bl(frameSize) + wxPoint(OUTER_BORDER / 2, -OUTER_BORDER / 2), MOUNTING_HOLE_R + PIN_TOLERANCE)
|
|
|
|
|
|
|
|
def jigMountingHoles(jigFrameSize, origin=wxPoint(0, 0)):
|
|
|
|
""" Get list of all mounting holes in a jig of given size """
|
|
|
|
w, h = jigFrameSize
|
|
|
|
holes = [
|
|
|
|
wxPoint(0, (w + INNER_BORDER) / 2),
|
|
|
|
wxPoint(0, -(w + INNER_BORDER) / 2),
|
|
|
|
wxPoint((h + INNER_BORDER) / 2, 0),
|
|
|
|
wxPoint(-(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, "Edge.Cuts"))
|
|
|
|
outerSubstrate.substrates = outerSubstrate.substrates.buffer(outerBorder)
|
|
|
|
tabs = []
|
|
|
|
for hole in holes:
|
2022-03-05 21:39:17 +01:00
|
|
|
tab, _ = outerSubstrate.tab(hole, centerpoint - hole, INNER_BORDER, maxHeight=fromMm(1000))
|
2020-11-05 19:04:30 +01:00
|
|
|
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, "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))
|
|
|
|
|
|
|
|
innerRing = createOffsetPolygon(board, - innerBorder).exterior.coords
|
|
|
|
if topSide:
|
|
|
|
innerRing = mirrorX(innerRing, centerpoint[0])
|
|
|
|
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)
|
|
|
|
subprocess.check_call(["openscad", "-o", outfile, infile])
|
|
|
|
|
|
|
|
def shapelyToSHAPE_POLY_SET(polygon):
|
|
|
|
p = pcbnew.SHAPE_POLY_SET()
|
|
|
|
p.AddOutline(linestringToKicad(polygon.exterior))
|
|
|
|
return p
|
|
|
|
|
|
|
|
def cutoutComponents(board, components):
|
|
|
|
topCutout = extractComponentPolygons(components, "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, "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)
|
|
|
|
|
2022-03-05 21:39:17 +01:00
|
|
|
def setStencilLayerVisibility(boardName):
|
|
|
|
if not isV6():
|
|
|
|
return
|
|
|
|
prlPath = os.path.splitext(boardName)[0] + ".kicad_prl"
|
|
|
|
with open(prlPath) 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)
|
|
|
|
prl["board"]["visible_layers"] = "ffc000c_7ffffffe"
|
|
|
|
prl["board"]["visible_items"] = [
|
|
|
|
1,
|
|
|
|
2,
|
|
|
|
3,
|
|
|
|
4,
|
|
|
|
9,
|
|
|
|
10,
|
|
|
|
12,
|
|
|
|
13,
|
|
|
|
15,
|
|
|
|
16,
|
|
|
|
19,
|
|
|
|
21,
|
|
|
|
22,
|
|
|
|
24,
|
|
|
|
25,
|
|
|
|
26,
|
|
|
|
27,
|
|
|
|
28,
|
|
|
|
29,
|
|
|
|
30,
|
|
|
|
32,
|
|
|
|
33,
|
|
|
|
34,
|
|
|
|
35
|
|
|
|
]
|
|
|
|
with open(prlPath, "w") as f:
|
|
|
|
json.dump(prl, f, indent=2)
|
|
|
|
pass
|
2020-11-05 19:04:30 +01:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2022-03-05 21:39:17 +01:00
|
|
|
setStencilLayerVisibility(stencilFile)
|
|
|
|
|
2020-11-05 19:04:30 +01:00
|
|
|
plotPlan = [
|
|
|
|
# name, id, comment
|
|
|
|
("PasteBottom", pcbnew.B_Paste, "Paste Bottom"),
|
|
|
|
("PasteTop", pcbnew.F_Paste, "Paste top"),
|
|
|
|
]
|
2022-03-05 21:39:17 +01:00
|
|
|
# get a copy of exportSettingsJlcpcb dictionary and
|
|
|
|
# exclude the Edge.Cuts layer for creation of stencil gerber files
|
|
|
|
exportSettings = exportSettingsJlcpcb.copy()
|
|
|
|
exportSettings["ExcludeEdgeLayer"] = True
|
2020-11-05 19:04:30 +01:00
|
|
|
gerberDir = os.path.join(outputdir, "gerber")
|
2022-03-05 21:39:17 +01:00
|
|
|
gerberImpl(stencilFile, gerberDir, plotPlan, False, exportSettings)
|
2020-11-05 19:04:30 +01:00
|
|
|
gerbers = [os.path.join(gerberDir, x) for x in os.listdir(gerberDir)]
|
|
|
|
subprocess.check_call(["zip", "-j",
|
|
|
|
os.path.join(outputdir, "gerbers.zip")] + gerbers)
|
|
|
|
|
|
|
|
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
|
|
|
|
"""
|
2022-03-05 21:39:17 +01:00
|
|
|
return [f for f in board.GetFootprints() if f.GetReference() in references]
|
2020-11-05 19:04:30 +01:00
|
|
|
|
2022-03-05 21:39:17 +01:00
|
|
|
def collectFootprintEdges(footprint, layerName):
|
2020-11-05 19:04:30 +01:00
|
|
|
"""
|
2022-03-05 21:39:17 +01:00
|
|
|
Return all edges on given layer in given footprint
|
2020-11-05 19:04:30 +01:00
|
|
|
"""
|
2022-03-05 21:39:17 +01:00
|
|
|
return [e for e in footprint.GraphicalItems() if e.GetLayerName() == layerName]
|
2020-11-05 19:04:30 +01:00
|
|
|
|
2022-03-05 21:39:17 +01:00
|
|
|
def extractComponentPolygons(footprints, srcLayer):
|
2020-11-05 19:04:30 +01:00
|
|
|
"""
|
|
|
|
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 = []
|
2022-03-05 21:39:17 +01:00
|
|
|
for f in footprints:
|
|
|
|
edges = collectFootprintEdges(f, srcLayer)
|
2020-11-05 19:04:30 +01:00
|
|
|
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)
|
|
|
|
topCutout = extractComponentPolygons(cutoutComponents, "F.CrtYd")
|
|
|
|
bottomCutout = extractComponentPolygons(cutoutComponents, "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"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
************************************************/
|