1#  This program is free software; you can redistribute it and/or modify
2#  it under the terms of the GNU General Public License as published by
3#  the Free Software Foundation; either version 2 of the License, or
4#  (at your option) any later version.
5#
6#  This program is distributed in the hope that it will be useful,
7#  but WITHOUT ANY WARRANTY; without even the implied warranty of
8#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9#  GNU General Public License for more details.
10#
11#  You should have received a copy of the GNU General Public License
12#  along with this program; if not, write to the Free Software
13#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
14#  MA 02110-1301, USA.
15#
16
17from __future__ import division
18import pcbnew
19
20import FootprintWizardBase
21import PadArray as PA
22
23
24class RowedGridArray(PA.PadGridArray):
25
26    def __init__(self, *args, **kwargs):
27        super(RowedGridArray, self).__init__(*args, **kwargs)
28
29    def NamingFunction(self, x, y):
30        pad_cnt = self.nx*self.ny
31
32        if self.ny == 1:
33            return x+1
34
35        if (y % 2) == 0:  # upper row, count down
36            return pad_cnt-x
37        else:  # lower row, count up
38            return x+1
39
40
41class RowedFootprint(FootprintWizardBase.FootprintWizard):
42
43    pad_count_key = 'pad count'
44    row_count_key = 'row count'
45    row_spacing_key = 'row spacing'
46    pad_length_key = 'pad length'
47    pad_width_key = 'pad width'
48    pad_pitch_key = 'pad pitch'
49
50    silkscreen_inside_key = 'silk screen inside'
51    outline_x_margin_key = 'outline x margin'
52    outline_y_margin_key = 'outline y margin'
53
54    def GenerateParameterList(self):
55        # defaults for a DIP package
56        self.AddParam("Pads", self.pad_count_key, self.uInteger, 24)
57        self.AddParam("Pads", self.row_count_key, self.uInteger, 2, min_value=1, max_value=2)
58
59        self.AddParam("Body", self.silkscreen_inside_key, self.uBool, False)
60        self.AddParam("Body", self.outline_x_margin_key, self.uMM, 0.5)
61        self.AddParam("Body", self.outline_y_margin_key, self.uMM, 0.5)
62
63    def CheckParameters(self):
64        self.CheckParam("Pads", self.pad_count_key, multiple=self.parameters['Pads'][self.row_count_key], info='Pads must be multiple of row count')
65
66    def BuildThisFootprint(self):
67        pads = self.parameters["Pads"]
68        body = self.parameters["Body"]
69        num_pads = pads[self.pad_count_key]
70        pad_length = pads[self.pad_length_key]
71        pad_width = pads[self.pad_width_key]
72        row_pitch = pads[self.row_spacing_key]
73        pad_pitch = pads[self.pad_pitch_key]
74        num_rows = pads[self.row_count_key]
75
76        pads_per_row = num_pads // num_rows
77
78        # add in the pads
79        pad = self.GetPad()
80
81        array = RowedGridArray(pad, pads_per_row, num_rows, pad_pitch, row_pitch)
82        array.AddPadsToModule(self.draw)
83
84        # draw the Silk Screen
85        Hsize = pad_pitch * (num_pads / num_rows - 1)
86        Vsize = row_pitch * (num_rows - 1)
87        pin1_posY = -Vsize / 2
88        pin1_posX = -Hsize / 2
89
90        pad_length = pads[self.pad_length_key]
91        pad_width = pads[self.pad_width_key]
92
93        ssx_offset = -pad_width / 2 - body[self.outline_x_margin_key]
94        ssy_offset = -pad_length / 2 - body[self.outline_y_margin_key]
95
96        if body[self.silkscreen_inside_key]:
97            ssy_offset *= -1
98
99        ssx = -pin1_posX - ssx_offset
100        ssy = -pin1_posY - ssy_offset
101
102        cmargin = self.draw.GetLineThickness()
103        self.draw.SetLineThickness( pcbnew.FromMM( 0.12 ) ) #Default per KLC F5.1 as of 12/2018
104        self.DrawBox(ssx, ssy)
105
106        # Courtyard
107        self.draw.SetLayer(pcbnew.F_CrtYd)
108        cclearance = pcbnew.FromMM(0.25)
109        sizex = (-pin1_posX + cclearance) * 2 + pad_width
110        sizey = (-pin1_posY + cclearance) * 2 + pad_length
111        # round size to nearest 0.02mm, rectangle will thus land on a 0.01mm grid
112        sizex = pcbnew.PutOnGridMM(sizex, 0.02)
113        sizey = pcbnew.PutOnGridMM(sizey, 0.02)
114        # set courtyard line thickness to the one defined in KLC
115        self.draw.SetLineThickness(pcbnew.FromMM(0.05))
116        self.draw.Box(0, 0, sizex, sizey)
117        # restore line thickness to previous value
118        self.draw.SetLineThickness(pcbnew.FromMM(cmargin))
119
120        #reference and value
121        text_size = self.GetTextSize()  # IPC nominal
122
123        if num_rows == 1:
124            text_py = ssy + text_size
125            self.draw.Value(0, -text_py, text_size)
126            self.draw.Reference(0, text_py, text_size)
127        else:
128            text_px = ssx + text_size
129            # self.draw.Value(text_px, 0, text_size, orientation_degree=90)
130            self.draw.Value(0, 0, text_size)
131            self.draw.Reference(-text_px, 0, text_size, orientation_degree=90)
132
133        # set the attribute
134        if self.GetName() == "S-DIP":
135            self.module.SetAttributes(pcbnew.PAD_ATTRIB_PTH)
136        elif self.GetName() == "SOIC":
137            self.module.SetAttributes(pcbnew.PAD_ATTRIB_SMD)
138
139class SDIPWizard(RowedFootprint):
140
141    def GetName(self):
142        return "S-DIP"
143
144    def GetDescription(self):
145        return "Single/Dual Inline Package Footprint Wizard"
146
147    def GenerateParameterList(self):
148        RowedFootprint.GenerateParameterList(self)
149
150        self.AddParam("Pads", self.pad_pitch_key, self.uMM, 2.54)
151        self.AddParam("Pads", self.pad_width_key, self.uMM, 1.2)
152        self.AddParam("Pads", self.pad_length_key, self.uMM, 2)
153        self.AddParam("Pads", self.row_spacing_key, self.uMM, 7.52)
154        self.AddParam("Pads", "drill size", self.uMM, 0.8)
155
156    def GetValue(self):
157        pads = self.parameters["Pads"]
158        rows = pads[self.row_count_key]
159        pad_count = pads[self.pad_count_key]
160        row_dist_mil = pcbnew.Iu2Mils(int(self.parameters["Pads"][self.row_spacing_key])) #int(self.parameters["Pads"][self.row_spacing_key] / 2.54 * 100)
161        pad_shape = ""
162
163        if pads[self.pad_width_key] != pads[self.pad_length_key]:
164            pad_shape = '_ELL'
165
166        if rows == 1:
167            name = "SIP"
168            return "%s-%d" % (name, pad_count)
169
170        name = "DIP"
171        return "%s-%d_%d%s" % (name, pad_count, row_dist_mil, pad_shape)
172
173    def GetPad(self):
174        pad_length = self.parameters["Pads"][self.pad_length_key]
175        pad_width = self.parameters["Pads"][self.pad_width_key]
176        drill = self.parameters["Pads"]["drill size"]
177        shape = pcbnew.PAD_SHAPE_CIRCLE
178
179        if pad_length != pad_width:
180            shape = pcbnew.PAD_SHAPE_OVAL
181
182        return PA.PadMaker(self.module).THPad(
183            pad_length, pad_width, drill, shape=shape)
184
185    def DrawBox(self, ssx, ssy):
186
187        if self.parameters["Pads"][self.row_count_key] == 2:
188
189            #  ----------
190            #  |8 7 6 5 |
191            #  >        |
192            #  |1 2 3 4 |
193            #  ----------
194
195            # draw the notch
196            notchWidth = ssy/1.5
197            notchHeight = self.draw.GetLineThickness()*3
198
199            # NotchedBox draws the notch on top. Rotate the box 90 degrees
200            # to have it on the left
201            self.draw.NotchedBox(0, 0, ssy*2, ssx*2, notchWidth, notchHeight, -90)
202        else:
203            #  -----------------
204            #  |1|2 3 4 5 6 7 8|
205            #  -----------------
206            self.draw.Box(0, 0, ssx*2, ssy*2)
207
208            #line between pin1 and pin2
209            pad_pitch = self.parameters["Pads"][self.pad_pitch_key]
210            pad_cnt = self.parameters["Pads"][self.pad_count_key]
211            line_x = ( pad_cnt/2 - 1) * pad_pitch
212            self.draw.VLine(-line_x, -ssy, ssy * 2)
213
214        return ssx, ssy
215
216SDIPWizard().register()
217
218
219class SOICWizard(RowedFootprint):
220
221    def GetName(self):
222        return "SOIC"
223
224    def GetDescription(self):
225        return "SOIC, MSOP, SSOP, TSSOP, etc, footprint wizard"
226
227    def GetValue(self):
228        pad_count = self.parameters["Pads"][self.pad_count_key]
229        return "%s-%d" % ("SOIC", pad_count)
230
231    def GenerateParameterList(self):
232        RowedFootprint.GenerateParameterList(self)
233
234        #and override some of them
235        self.AddParam("Pads", self.pad_pitch_key, self.uMM, 1.27)
236        self.AddParam("Pads", self.pad_width_key, self.uMM, 0.6)
237        self.AddParam("Pads", self.pad_length_key, self.uMM, 2.2)
238        self.AddParam("Pads", self.row_spacing_key, self.uMM, 5.2)
239
240    def GetPad(self):
241        pad_length = self.parameters["Pads"][self.pad_length_key]
242        pad_width = self.parameters["Pads"][self.pad_width_key]
243        return PA.PadMaker(self.module).SMDPad(
244            pad_length, pad_width, shape=pcbnew.PAD_SHAPE_RECT)
245
246    def DrawBox(self, ssx, ssy):
247
248        #  ----------
249        #  |8 7 6 5 |
250        #  |1 2 3 4 |
251        #  \---------
252
253        setback = pcbnew.FromMM(0.8)
254
255        if setback > ssy:
256            setback = ssy
257
258        self.draw.BoxWithDiagonalAtCorner(0, 0, ssx*2, ssy*2, setback, self.draw.flipY)
259
260SOICWizard().register()
261