KiCad PCB EDA Suite
Loading...
Searching...
No Matches
export_d356.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2011-2013 Lorenzo Marcantonio <[email protected]>
5 * Copyright (C) 2004-2023 KiCad Developers, see change_log.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
30#include <confirm.h>
31#include <gestfich.h>
32#include <kiface_base.h>
33#include <pcb_edit_frame.h>
34#include <trigo.h>
35#include <build_version.h>
36#include <macros.h>
38#include <locale_io.h>
39#include <board.h>
41#include <footprint.h>
42#include <pad.h>
43#include <pcb_track.h>
44#include <vector>
45#include <cctype>
46#include <math/util.h> // for KiROUND
47#include <export_d356.h>
48#include <wx/filedlg.h>
49
50
51
52// Compute the access code for a pad. Returns -1 if there is no copper
53static int compute_pad_access_code( BOARD *aPcb, LSET aLayerMask )
54{
55 // Non-copper is not interesting here
56 aLayerMask &= LSET::AllCuMask();
57 if( !aLayerMask.any() )
58 return -1;
59
60 // Traditional TH pad
61 if( aLayerMask[F_Cu] && aLayerMask[B_Cu] )
62 return 0;
63
64 // Front SMD pad
65 if( aLayerMask[F_Cu] )
66 return 1;
67
68 // Back SMD pad
69 if( aLayerMask[B_Cu] )
70 return aPcb->GetCopperLayerCount();
71
72 // OK, we have an inner-layer only pad (and I have no idea about
73 // what could be used for); anyway, find the first copper layer
74 // it's on
75 for( int layer = In1_Cu; layer < B_Cu; ++layer )
76 {
77 if( aLayerMask[layer] )
78 return layer + 1;
79 }
80
81 // This shouldn't happen
82 return -1;
83}
84
85/* Convert and clamp a size from IU to decimils */
86static int iu_to_d356(int iu, int clamp)
87{
88 int val = KiROUND( iu / ( pcbIUScale.IU_PER_MILS / 10 ) );
89 if( val > clamp ) return clamp;
90 if( val < -clamp ) return -clamp;
91 return val;
92}
93
94/* Extract the D356 record from the footprints (pads) */
95static void build_pad_testpoints( BOARD *aPcb, std::vector <D356_RECORD>& aRecords )
96{
97 VECTOR2I origin = aPcb->GetDesignSettings().GetAuxOrigin();
98
99 for( FOOTPRINT* footprint : aPcb->Footprints() )
100 {
101 for( PAD* pad : footprint->Pads() )
102 {
103 D356_RECORD rk;
104 rk.access = compute_pad_access_code( aPcb, pad->GetLayerSet() );
105
106 // It could be a mask only pad, we only handle pads with copper here
107 if( rk.access != -1 )
108 {
109 rk.netname = pad->GetNetname();
110 rk.pin = pad->GetNumber();
111 rk.refdes = footprint->GetReference();
112 rk.midpoint = false; // XXX MAYBE need to be computed (how?)
113 const VECTOR2I& drill = pad->GetDrillSize();
114 rk.drill = std::min( drill.x, drill.y );
115 rk.hole = (rk.drill != 0);
116 rk.smd = pad->GetAttribute() == PAD_ATTRIB::SMD
117 || pad->GetAttribute() == PAD_ATTRIB::CONN;
118 rk.mechanical = ( pad->GetAttribute() == PAD_ATTRIB::NPTH );
119 rk.x_location = pad->GetPosition().x - origin.x;
120 rk.y_location = origin.y - pad->GetPosition().y;
121 rk.x_size = pad->GetSize().x;
122
123 // Rule: round pads have y = 0
124 if( pad->GetShape() == PAD_SHAPE::CIRCLE )
125 rk.y_size = 0;
126 else
127 rk.y_size = pad->GetSize().y;
128
129 rk.rotation = - pad->GetOrientation().AsDegrees();
130
131 if( rk.rotation < 0 )
132 rk.rotation += 360;
133
134 // the value indicates which sides are *not* accessible
135 rk.soldermask = 3;
136
137 if( pad->GetLayerSet()[F_Mask] )
138 rk.soldermask &= ~1;
139
140 if( pad->GetLayerSet()[B_Mask] )
141 rk.soldermask &= ~2;
142
143 aRecords.push_back( rk );
144 }
145 }
146 }
147}
148
149/* Compute the access code for a via. In D-356 layers are numbered from 1 up,
150 where '1' is the 'primary side' (usually the component side);
151 '0' means 'both sides', and other layers follows in an unspecified order */
152static int via_access_code( BOARD *aPcb, int top_layer, int bottom_layer )
153{
154 // Easy case for through vias: top_layer is component, bottom_layer is
155 // solder, access code is 0
156 if( (top_layer == F_Cu) && (bottom_layer == B_Cu) )
157 return 0;
158
159 // Blind via, reachable from front
160 if( top_layer == F_Cu )
161 return 1;
162
163 // Blind via, reachable from bottom
164 if( bottom_layer == B_Cu )
165 return aPcb->GetCopperLayerCount();
166
167 // It's a buried via, accessible from some inner layer
168 // (maybe could be used for testing before laminating? no idea)
169 return bottom_layer + 1; // XXX is this correct?
170}
171
172/* Extract the D356 record from the vias */
173static void build_via_testpoints( BOARD *aPcb, std::vector <D356_RECORD>& aRecords )
174{
175 VECTOR2I origin = aPcb->GetDesignSettings().GetAuxOrigin();
176
177 // Enumerate all the track segments and keep the vias
178 for( auto track : aPcb->Tracks() )
179 {
180 if( track->Type() == PCB_VIA_T )
181 {
182 PCB_VIA *via = static_cast<PCB_VIA*>( track );
183 NETINFO_ITEM *net = track->GetNet();
184
185 D356_RECORD rk;
186 rk.smd = false;
187 rk.hole = true;
188 if( net )
189 rk.netname = net->GetNetname();
190 else
191 rk.netname = wxEmptyString;
192 rk.refdes = wxT("VIA");
193 rk.pin = wxT("");
194 rk.midpoint = true; // Vias are always midpoints
195 rk.drill = via->GetDrillValue();
196 rk.mechanical = false;
197
198 PCB_LAYER_ID top_layer, bottom_layer;
199
200 via->LayerPair( &top_layer, &bottom_layer );
201
202 rk.access = via_access_code( aPcb, top_layer, bottom_layer );
203 rk.x_location = via->GetPosition().x - origin.x;
204 rk.y_location = origin.y - via->GetPosition().y;
205 rk.x_size = via->GetWidth();
206 rk.y_size = 0; // Round so height = 0
207 rk.rotation = 0;
208 rk.soldermask = 3; // XXX always tented?
209
210 aRecords.push_back( rk );
211 }
212 }
213}
214
215/* Add a new netname to the d356 canonicalized list */
216static const wxString intern_new_d356_netname( const wxString &aNetname,
217 std::map<wxString, wxString> &aMap, std::set<wxString> &aSet )
218{
219 wxString canon;
220
221 for( size_t ii = 0; ii < aNetname.Len(); ++ii )
222 {
223 // Rule: we can only use the standard ASCII, control excluded
224 wxUniChar ch = aNetname[ii];
225
226 if( ch > 126 || !std::isgraph( static_cast<unsigned char>( ch ) ) )
227 ch = '?';
228
229 canon += ch;
230 }
231
232 // Rule: only uppercase (unofficial, but known to give problems
233 // otherwise)
234 canon.MakeUpper();
235
236 // Rule: maximum length is 14 characters, otherwise we keep the tail
237 if( canon.size() > 14 )
238 {
239 canon = canon.Right( 14 );
240 }
241
242 // Check if it's still unique
243 if( aSet.count( canon ) )
244 {
245 // Nope, need to uniquify it, trim it more and add a number
246 wxString base( canon );
247 if( base.size() > 10 )
248 {
249 base = base.Right( 10 );
250 }
251
252 int ctr = 0;
253 do
254 {
255 ++ctr;
256 canon = base;
257 canon << '#' << ctr;
258 } while ( aSet.count( canon ) );
259 }
260
261 // Register it
262 aMap[aNetname] = canon;
263 aSet.insert( canon );
264 return canon;
265}
266
267/* Write all the accumuled data to the file in D356 format */
268void IPC356D_WRITER::write_D356_records( std::vector <D356_RECORD> &aRecords, FILE* aFile )
269{
270 // Sanified and shorted network names and set of short names
271 std::map<wxString, wxString> d356_net_map;
272 std::set<wxString> d356_net_set;
273
274 for( unsigned i = 0; i < aRecords.size(); i++ )
275 {
276 D356_RECORD &rk = aRecords[i];
277
278 // Try to sanify the network name (there are limits on this), if
279 // not already done. Also 'empty' net are marked as N/C, as
280 // specified.
281 wxString d356_net( wxT( "N/C" ) );
282
283 if( !rk.netname.empty() )
284 {
285 d356_net = d356_net_map[rk.netname];
286
287 if( d356_net.empty() )
288 d356_net = intern_new_d356_netname( rk.netname, d356_net_map, d356_net_set );
289 }
290
291 // Choose the best record type
292 int rktype;
293
294 if( rk.smd )
295 rktype = 327;
296 else
297 {
298 if( rk.mechanical )
299 rktype = 367;
300 else
301 rktype = 317;
302 }
303
304 // Operation code, signal and component
305 fprintf( aFile, "%03d%-14.14s %-6.6s%c%-4.4s%c",
306 rktype, TO_UTF8(d356_net),
307 TO_UTF8(rk.refdes),
308 rk.pin.empty()?' ':'-',
309 TO_UTF8(rk.pin),
310 rk.midpoint?'M':' ' );
311
312 // Hole definition
313 if( rk.hole )
314 {
315 fprintf( aFile, "D%04d%c",
316 iu_to_d356( rk.drill, 9999 ),
317 rk.mechanical ? 'U':'P' );
318 }
319 else
320 fprintf( aFile, " " );
321
322 // Test point access
323 fprintf( aFile, "A%02dX%+07dY%+07dX%04dY%04dR%03d",
324 rk.access,
325 iu_to_d356( rk.x_location, 999999 ),
326 iu_to_d356( rk.y_location, 999999 ),
327 iu_to_d356( rk.x_size, 9999 ),
328 iu_to_d356( rk.y_size, 9999 ),
329 rk.rotation );
330
331 // Soldermask
332 fprintf( aFile, "S%d\n", rk.soldermask );
333 }
334}
335
336
337void IPC356D_WRITER::Write( const wxString& aFilename )
338{
339 FILE* file = nullptr;
340 LOCALE_IO toggle; // Switch the locale to standard C
341
342 if( ( file = wxFopen( aFilename, wxT( "wt" ) ) ) == nullptr )
343 {
344 wxString details;
345 details.Printf( wxT( "The file %s could not be opened for writing." ), aFilename );
346 DisplayErrorMessage( m_parent, wxT( "Could not write IPC-356D file!" ), details );
347 return;
348 }
349
350 // This will contain everything needed for the 356 file
351 std::vector<D356_RECORD> d356_records;
352
353 build_via_testpoints( m_pcb, d356_records );
354
355 build_pad_testpoints( m_pcb, d356_records );
356
357 // Code 00 AFAIK is ASCII, CUST 0 is decimils/degrees
358 // CUST 1 would be metric but gerbtool simply ignores it!
359 fprintf( file, "P CODE 00\n" );
360 fprintf( file, "P UNITS CUST 0\n" );
361 fprintf( file, "P arrayDim N\n" );
362 write_D356_records( d356_records, file );
363 fprintf( file, "999\n" );
364
365 fclose( file );
366}
367
368
369void PCB_EDIT_FRAME::GenD356File( wxCommandEvent& aEvent )
370{
371 wxFileName fn = GetBoard()->GetFileName();
372 wxString ext, wildcard;
373
375 wildcard = FILEEXT::IpcD356FileWildcard();
376 fn.SetExt( ext );
377
378 wxString pro_dir = wxPathOnly( Prj().GetProjectFullName() );
379
380 wxFileDialog dlg( this, _( "Export D-356 Test File" ), pro_dir,
381 fn.GetFullName(), wildcard,
382 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
383
384 if( dlg.ShowModal() == wxID_CANCEL )
385 return;
386
387 IPC356D_WRITER writer( GetBoard(), this );
388
389 writer.Write( dlg.GetPath() );
390}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:109
const VECTOR2I & GetAuxOrigin()
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:276
FOOTPRINTS & Footprints()
Definition: board.h:318
int GetCopperLayerCount() const
Definition: board.cpp:665
TRACKS & Tracks()
Definition: board.h:315
const wxString & GetFileName() const
Definition: board.h:313
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:806
Wrapper to expose an API for writing IPC-D356 files.
Definition: export_d356.h:54
void Write(const wxString &aFilename)
Generates and writes the netlist to a given path.
wxWindow * m_parent
Definition: export_d356.h:75
void write_D356_records(std::vector< D356_RECORD > &aRecords, FILE *aFile)
Writes a list of records to the given output stream.
BOARD * m_pcb
Definition: export_d356.h:74
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:49
LSET is a set of PCB_LAYER_IDs.
Definition: layer_ids.h:573
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:863
Handle the data for a net.
Definition: netinfo.h:56
const wxString & GetNetname() const
Definition: netinfo.h:114
Definition: pad.h:59
BOARD * GetBoard() const
void GenD356File(wxCommandEvent &event)
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:305
This file is part of the common library.
#define _(s)
static void build_pad_testpoints(BOARD *aPcb, std::vector< D356_RECORD > &aRecords)
Definition: export_d356.cpp:95
static void build_via_testpoints(BOARD *aPcb, std::vector< D356_RECORD > &aRecords)
static int iu_to_d356(int iu, int clamp)
Definition: export_d356.cpp:86
static const wxString intern_new_d356_netname(const wxString &aNetname, std::map< wxString, wxString > &aMap, std::set< wxString > &aSet)
static int via_access_code(BOARD *aPcb, int top_layer, int bottom_layer)
static int compute_pad_access_code(BOARD *aPcb, LSET aLayerMask)
Definition: export_d356.cpp:53
static const std::string IpcD356FileExtension
static wxString IpcD356FileWildcard()
PROJECT & Prj()
Definition: kicad.cpp:591
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
@ B_Mask
Definition: layer_ids.h:107
@ B_Cu
Definition: layer_ids.h:96
@ F_Mask
Definition: layer_ids.h:108
@ In1_Cu
Definition: layer_ids.h:66
@ F_Cu
Definition: layer_ids.h:65
This file contains miscellaneous commonly used macros and functions.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
@ SMD
Smd pad, appears on the solder paste layer (default)
@ CONN
Like smd, does not appear on the solder paste layer (default) Note: also has a special attribute in G...
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:391
int x_location
Definition: export_d356.h:42
wxString refdes
Definition: export_d356.h:34
bool midpoint
Definition: export_d356.h:36
bool mechanical
Definition: export_d356.h:38
int y_location
Definition: export_d356.h:43
wxString pin
Definition: export_d356.h:35
int soldermask
Definition: export_d356.h:40
wxString netname
Definition: export_d356.h:33
const double IU_PER_MILS
Definition: base_units.h:78
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:97
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:85
Definition of file extensions used in Kicad.