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 The KiCad Developers, see AUTHORS.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
29#include "export_d356.h"
30
31#include <vector>
32#include <cctype>
33
34#include <wx/checkbox.h>
35#include <wx/filedlg.h>
36#include <wx/filedlgcustomize.h>
37#include <wx/msgdlg.h>
38#include <kiplatform/ui.h>
39
40#include <confirm.h>
41#include <gestfich.h>
42#include <kiface_base.h>
43#include <pcb_edit_frame.h>
44#include <trigo.h>
45#include <build_version.h>
46#include <macros.h>
48#include <locale_io.h>
49#include <board.h>
51#include <footprint.h>
52#include <layer_range.h>
53#include <pad.h>
54#include <pcb_track.h>
55#include <string_utils.h>
56
57#include <math/util.h> // for KiROUND
59
60
61// Compute the access code for a pad. Returns -1 if there is no copper
62static int compute_pad_access_code( BOARD *aPcb, LSET aLayerMask )
63{
64 // Non-copper is not interesting here
65 aLayerMask &= LSET::AllCuMask();
66
67 if( !aLayerMask.any() )
68 return -1;
69
70 // Traditional TH pad
71 if( aLayerMask[F_Cu] && aLayerMask[B_Cu] )
72 return 0;
73
74 // Front SMD pad
75 if( aLayerMask[F_Cu] )
76 return 1;
77
78 // Back SMD pad
79 if( aLayerMask[B_Cu] )
80 return aPcb->GetCopperLayerCount();
81
82 // OK, we have an inner-layer only pad (and I have no idea about
83 // what could be used for); anyway, find the first copper layer
84 // it's on
85 for( PCB_LAYER_ID layer : LAYER_RANGE( In1_Cu, B_Cu, aPcb->GetCopperLayerCount() ) )
86 {
87 if( aLayerMask[layer] )
88 return layer + 1;
89 }
90
91 // This shouldn't happen
92 return -1;
93}
94
95/* Convert and clamp a size from IU to decimils */
96static int iu_to_d356(int iu, int clamp)
97{
98 int val = KiROUND( static_cast<double>( iu ) / ( pcbIUScale.IU_PER_MILS / 10.0 ) );
99 if( val > clamp ) return clamp;
100 if( val < -clamp ) return -clamp;
101 return val;
102}
103
104/* Extract the D356 record from the footprints (pads) */
105void IPC356D_WRITER::build_pad_testpoints( BOARD *aPcb, std::vector <D356_RECORD>& aRecords )
106{
107 VECTOR2I origin = aPcb->GetDesignSettings().GetAuxOrigin();
108
109 for( FOOTPRINT* footprint : aPcb->Footprints() )
110 {
111 for( PAD* pad : footprint->Pads() )
112 {
113 D356_RECORD rk;
114 rk.access = compute_pad_access_code( aPcb, pad->GetLayerSet() );
115
116 // It could be a mask only pad, we only handle pads with copper here
117 if( rk.access == -1 )
118 continue;
119
121 continue;
122
123 rk.netname = pad->GetNetname();
124 rk.pin = pad->GetNumber();
125 rk.refdes = footprint->GetReference();
126 rk.midpoint = false; // XXX MAYBE need to be computed (how?)
127 const VECTOR2I& drill = pad->GetDrillSize();
128 rk.drill = std::min( drill.x, drill.y );
129 rk.hole = (rk.drill != 0);
130 rk.smd = pad->GetAttribute() == PAD_ATTRIB::SMD
131 || pad->GetAttribute() == PAD_ATTRIB::CONN;
132 rk.mechanical = ( pad->GetAttribute() == PAD_ATTRIB::NPTH );
133 rk.x_location = pad->GetPosition().x - origin.x;
134 rk.y_location = origin.y - pad->GetPosition().y;
135
136 PCB_LAYER_ID accessLayer = footprint->IsFlipped() ? B_Cu : F_Cu;
137 rk.x_size = pad->GetSize( accessLayer ).x;
138
139 // Rule: round pads have y = 0
140 if( pad->GetShape( accessLayer ) == PAD_SHAPE::CIRCLE )
141 rk.y_size = 0;
142 else
143 rk.y_size = pad->GetSize( accessLayer ).y;
144
145 rk.rotation = - pad->GetOrientation().AsDegrees();
146
147 if( rk.rotation < 0 )
148 rk.rotation += 360;
149
150 // the value indicates which sides are *not* accessible
151 rk.soldermask = 3;
152
153 if( pad->GetLayerSet()[F_Mask] )
154 rk.soldermask &= ~1;
155
156 if( pad->GetLayerSet()[B_Mask] )
157 rk.soldermask &= ~2;
158
159 aRecords.push_back( std::move( rk ) );
160 }
161 }
162}
163
164/* Compute the access code for a via. In D-356 layers are numbered from 1 up,
165 where '1' is the 'primary side' (usually the component side);
166 '0' means 'both sides', and other layers follows in an unspecified order */
167static int via_access_code( BOARD *aPcb, int top_layer, int bottom_layer )
168{
169 // Easy case for through vias: top_layer is component, bottom_layer is
170 // solder, access code is 0
171 if( (top_layer == F_Cu) && (bottom_layer == B_Cu) )
172 return 0;
173
174 // Blind via, reachable from front
175 if( top_layer == F_Cu )
176 return 1;
177
178 // Blind via, reachable from bottom
179 if( bottom_layer == B_Cu )
180 return aPcb->GetCopperLayerCount();
181
182 // It's a buried via, accessible from some inner layer
183 // (maybe could be used for testing before laminating? no idea)
184 return ( top_layer / 2 ) + 1;
185}
186
187/* Extract the D356 record from the vias */
188static void build_via_testpoints( BOARD *aPcb, std::vector <D356_RECORD>& aRecords )
189{
190 VECTOR2I origin = aPcb->GetDesignSettings().GetAuxOrigin();
191
192 // Enumerate all the track segments and keep the vias
193 for( auto track : aPcb->Tracks() )
194 {
195 if( track->Type() == PCB_VIA_T )
196 {
197 PCB_VIA *via = static_cast<PCB_VIA*>( track );
198 NETINFO_ITEM *net = track->GetNet();
199
200 D356_RECORD rk;
201 rk.smd = false;
202 rk.hole = true;
203 if( net )
204 rk.netname = net->GetNetname();
205 else
206 rk.netname = wxEmptyString;
207 rk.refdes = wxT("VIA");
208 rk.pin = wxT("");
209 rk.midpoint = true; // Vias are always midpoints
210 rk.drill = via->GetDrillValue();
211 rk.mechanical = false;
212
213 PCB_LAYER_ID top_layer, bottom_layer;
214
215 via->LayerPair( &top_layer, &bottom_layer );
216
217 rk.access = via_access_code( aPcb, top_layer, bottom_layer );
218 rk.x_location = via->GetPosition().x - origin.x;
219 rk.y_location = origin.y - via->GetPosition().y;
220
221 // The record has a single size for vias, so take the smaller of the front and back
222 if( via->Padstack().Mode() != PADSTACK::MODE::NORMAL )
223 rk.x_size = std::min( via->GetWidth( F_Cu ), via->GetWidth( B_Cu ) );
224 else
225 rk.x_size = via->GetWidth( PADSTACK::ALL_LAYERS );
226
227 rk.y_size = 0; // Round so height = 0
228 rk.rotation = 0;
229
230 if( via->IsTented( F_Mask ) )
231 rk.soldermask |= 1;
232 if( via->IsTented( B_Mask ) )
233 rk.soldermask |= 2;
234
235 aRecords.push_back( rk );
236 }
237 }
238}
239
240/* Add a new netname to the d356 canonicalized list */
241static const wxString intern_new_d356_netname( const wxString &aNetname,
242 std::map<wxString, wxString> &aMap, std::set<wxString> &aSet )
243{
244 wxString canon;
245
246 for( size_t ii = 0; ii < aNetname.Len(); ++ii )
247 {
248 // Rule: we can only use the standard ASCII, control excluded
249 wxUniChar ch = aNetname[ii];
250
251 if( ch > 126 || !std::isgraph( static_cast<unsigned char>( ch ) ) )
252 ch = '?';
253
254 canon += ch;
255 }
256
257 // Rule: only uppercase (unofficial, but known to give problems
258 // otherwise)
259 canon.MakeUpper();
260
261 // Rule: maximum length is 14 characters, otherwise we keep the tail
262 if( canon.size() > 14 )
263 {
264 canon = canon.Right( 14 );
265 }
266
267 // Check if it's still unique
268 if( aSet.count( canon ) )
269 {
270 // Nope, need to uniquify it, trim it more and add a number
271 wxString base( canon );
272 if( base.size() > 10 )
273 {
274 base = base.Right( 10 );
275 }
276
277 int ctr = 0;
278 do
279 {
280 ++ctr;
281 canon = base;
282 canon << '#' << ctr;
283 } while ( aSet.count( canon ) );
284 }
285
286 // Register it
287 aMap[aNetname] = canon;
288 aSet.insert( canon );
289 return canon;
290}
291
292/* Write all the accumuled data to the file in D356 format */
293void IPC356D_WRITER::write_D356_records( std::vector <D356_RECORD> &aRecords, FILE* aFile )
294{
295 // Sanified and shorted network names and set of short names
296 std::map<wxString, wxString> d356_net_map;
297 std::set<wxString> d356_net_set;
298
299 for( unsigned i = 0; i < aRecords.size(); i++ )
300 {
301 D356_RECORD &rk = aRecords[i];
302
303 // Try to sanify the network name (there are limits on this), if
304 // not already done. Also 'empty' net are marked as N/C, as
305 // specified.
306 wxString d356_net( wxT( "N/C" ) );
307
308 if( !rk.netname.empty() )
309 {
310 d356_net = d356_net_map[rk.netname];
311
312 if( d356_net.empty() )
313 d356_net = intern_new_d356_netname( rk.netname, d356_net_map, d356_net_set );
314 }
315
316 // Choose the best record type
317 int rktype;
318
319 if( rk.smd )
320 rktype = 327;
321 else
322 {
323 if( rk.mechanical )
324 rktype = 367;
325 else if( rk.access == 0 ) // This is a through-hole via
326 rktype = 317;
327 else // All others are either blind or buried
328 rktype = 307;
329 }
330
331 // Operation code, signal and component
332 fprintf( aFile, "%03d%-14.14s %-6.6s%c%-4.4s%c",
333 rktype, TO_UTF8(d356_net),
334 TO_UTF8(rk.refdes),
335 rk.pin.empty()?' ':'-',
336 TO_UTF8(rk.pin),
337 rk.midpoint?'M':' ' );
338
339 // Hole definition
340 if( rk.hole )
341 {
342 fprintf( aFile, "D%04d%c",
343 iu_to_d356( rk.drill, 9999 ),
344 rk.mechanical ? 'U':'P' );
345 }
346 else
347 fprintf( aFile, " " );
348
349 // Test point access
350 fprintf( aFile, "A%02dX%+07dY%+07dX%04dY%04dR%03d",
351 rk.access,
352 iu_to_d356( rk.x_location, 999999 ),
353 iu_to_d356( rk.y_location, 999999 ),
354 iu_to_d356( rk.x_size, 9999 ),
355 iu_to_d356( rk.y_size, 9999 ),
356 rk.rotation );
357
358 // Soldermask
359 fprintf( aFile, "S%d\n", rk.soldermask );
360 }
361}
362
363
364bool IPC356D_WRITER::Write( const wxString& aFilename )
365{
366 FILE* file = nullptr;
367 LOCALE_IO toggle; // Switch the locale to standard C
368
369 if( ( file = wxFopen( aFilename, wxT( "wt" ) ) ) == nullptr )
370 {
371 return false;
372 }
373
374 // This will contain everything needed for the 356 file
375 std::vector<D356_RECORD> d356_records;
376
377 build_via_testpoints( m_pcb, d356_records );
378
379 build_pad_testpoints( m_pcb, d356_records );
380
381 // Code 00 AFAIK is ASCII, CUST 0 is decimils/degrees
382 // CUST 1 would be metric but gerbtool simply ignores it!
383 fprintf( file, "P CODE 00\n" );
384 fprintf( file, "P UNITS CUST 0\n" );
385 fprintf( file, "P arrayDim N\n" );
386 write_D356_records( d356_records, file );
387 fprintf( file, "999\n" );
388
389 fclose( file );
390
391 return true;
392}
393
394
395
396
397// Subclass for wxFileDialogCustomizeHook to add the checkbox
398class D365_CUSTOMIZE_HOOK : public wxFileDialogCustomizeHook
399{
400public:
401
402public:
403 D365_CUSTOMIZE_HOOK( bool aDoNotExportUnconnectedPads = false ) :
404 m_doNotExportUnconnectedPads( aDoNotExportUnconnectedPads ),
405 m_cb(nullptr)
406 {};
407
408 virtual void AddCustomControls( wxFileDialogCustomize& customizer ) override
409 {
410#ifdef __WXMAC__
411 customizer.AddStaticText( wxT( "\n\n" ) ); // Increase height of static box
412#endif
413
414 m_cb = customizer.AddCheckBox( _( "Do not export unconnected pads" ) );
416 }
417
418 virtual void TransferDataFromCustomControls() override
419 {
421 }
422
424private:
425
427 wxFileDialogCheckBox* m_cb;
428
430};
431
432
434{
435 wxFileName fn = m_frame->GetBoard()->GetFileName();
436 wxString ext, wildcard;
437
439 wildcard = FILEEXT::IpcD356FileWildcard();
440 fn.SetExt( ext );
441
442 wxString pro_dir = wxPathOnly( m_frame->Prj().GetProjectFullName() );
443
444 wxFileDialog dlg( m_frame, _( "Generate IPC-D-356 netlist file" ), pro_dir, fn.GetFullName(),
445 wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
446 D365_CUSTOMIZE_HOOK customizeHook( m_frame->GetPcbNewSettings()->m_ExportD356.doNotExportUnconnectedPads );
447 dlg.SetCustomizeHook( customizeHook );
448
450
451 if( dlg.ShowModal() == wxID_CANCEL )
452 return 0;
453
454 IPC356D_WRITER writer( m_frame->GetBoard() );
455 bool doNotExportUnconnectedPads = customizeHook.GetDoNotExportUnconnectedPads();
456 writer.SetDoNotExportUnconnectedPads( doNotExportUnconnectedPads );
457 m_frame->GetPcbNewSettings()->m_ExportD356.doNotExportUnconnectedPads = doNotExportUnconnectedPads;
458
459 if( writer.Write( dlg.GetPath() ) )
460 {
461 DisplayInfoMessage( m_frame, wxString::Format( _( "IPC-D-356 netlist file created:\n'%s'." ),
462 dlg.GetPath() ) );
463 }
464 else
465 {
466 DisplayError( m_frame, wxString::Format( _( "Failed to create file '%s'." ),
467 dlg.GetPath() ) );
468 }
469
470 return 0;
471}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
const VECTOR2I & GetAuxOrigin() const
int GenD356File(const TOOL_EVENT &aEvent)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
int GetCopperLayerCount() const
Definition board.cpp:921
const FOOTPRINTS & Footprints() const
Definition board.h:363
const TRACKS & Tracks() const
Definition board.h:361
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1084
virtual void TransferDataFromCustomControls() override
D365_CUSTOMIZE_HOOK(bool aDoNotExportUnconnectedPads=false)
wxDECLARE_NO_COPY_CLASS(D365_CUSTOMIZE_HOOK)
wxFileDialogCheckBox * m_cb
bool GetDoNotExportUnconnectedPads() const
virtual void AddCustomControls(wxFileDialogCustomize &customizer) override
Wrapper to expose an API for writing IPC-D356 files.
Definition export_d356.h:54
bool m_doNotExportUnconnectedPads
Definition export_d356.h:92
void write_D356_records(std::vector< D356_RECORD > &aRecords, FILE *aFile)
Writes a list of records to the given output stream.
void build_pad_testpoints(BOARD *aPcb, std::vector< D356_RECORD > &aRecords)
void SetDoNotExportUnconnectedPads(bool aDoNotExportUnconnectedPads)
Sets whether unconnected pads should be exported.
Definition export_d356.h:79
bool Write(const wxString &aFilename)
Generates and writes the netlist to a given path.
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:41
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:608
Handle the data for a net.
Definition netinfo.h:54
const wxString & GetNetname() const
Definition netinfo.h:112
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:247
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:171
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:55
Generic, UI-independent tool event.
Definition tool_event.h:171
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition confirm.cpp:230
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:177
This file is part of the common library.
#define _(s)
static void build_via_testpoints(BOARD *aPcb, std::vector< D356_RECORD > &aRecords)
static int iu_to_d356(int iu, int clamp)
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)
static const std::string IpcD356FileExtension
static wxString IpcD356FileWildcard()
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ F_Mask
Definition layer_ids.h:97
@ In1_Cu
Definition layer_ids.h:66
@ F_Cu
Definition layer_ids.h:64
This file contains miscellaneous commonly used macros and functions.
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:717
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
@ CONN
Like smd, does not appear on the solder paste layer (default) Note: also has a special attribute in G...
Definition padstack.h:100
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
wxString refdes
Definition export_d356.h:34
bool mechanical
Definition export_d356.h:38
wxString pin
Definition export_d356.h:35
wxString netname
Definition export_d356.h:33
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
Definition of file extensions used in Kicad.