34#include <wx/checkbox.h>
35#include <wx/filedlg.h>
36#include <wx/filedlgcustomize.h>
77 if( !aLayerMask.any() )
81 if( aLayerMask[
F_Cu] && aLayerMask[
B_Cu] )
85 if( aLayerMask[
F_Cu] )
89 if( aLayerMask[
B_Cu] )
97 if( aLayerMask[layer] )
108 int val =
KiROUND(
static_cast<double>( iu ) / (
pcbIUScale.IU_PER_MILS / 10.0 ) );
109 if( val > clamp )
return clamp;
110 if( val < -clamp )
return -clamp;
116 std::vector<D356_RECORD>& aRecords )
125 const PAD*
pad = point.m_pad;
138 rk.
pin =
pad->GetNumber();
143 rk.
drill = point.m_drill;
151 rk.
x_size =
pad->GetSize( accessLayer ).x;
157 rk.
y_size =
pad->GetSize( accessLayer ).y;
167 if( point.m_frontMask )
170 if( point.m_backMask )
173 aRecords.push_back( std::move( rk ) );
184 if( (top_layer ==
F_Cu) && (bottom_layer ==
B_Cu) )
188 if( top_layer ==
F_Cu )
192 if( bottom_layer ==
B_Cu )
202 std::vector<D356_RECORD>& aRecords )
226 rk.
drill = point.m_drill;
231 via->LayerPair( &top_layer, &bottom_layer );
267 aRecords.push_back( rk );
277 for( wxUniChar ch : aNetname )
279 if( ch > 126 || !std::isgraph(
static_cast<unsigned char>( ch ) ) )
281 else if( aUpper && ch >=
'a' && ch <=
'z' )
282 ch =
static_cast<char>( ch.GetValue() -
'a' +
'A' );
294 static const char digits[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
295 wxString alias( wxT(
"M" ) );
297 for(
int div = 36 * 36 * 36; div > 0; div /= 36 )
298 alias += digits[( aIndex / div ) % 36];
307 const wxString nc( wxT(
"N/C" ) );
310 std::map<wxString, wxString> d356_net_map;
311 std::set<wxString> d356_net_set = { nc };
312 std::map<wxString, int> canonCount;
317 if( !rk.netname.empty() && d356_net_map.emplace( rk.netname, wxEmptyString ).second )
322 for(
const auto& [candidate, count] : canonCount )
323 d356_net_set.insert( candidate );
326 for(
auto& [netname, canon] : d356_net_map )
330 if( candidate.size() <= 14 && candidate != nc && canonCount[candidate] == 1 )
336 for(
auto& [netname, canon] : d356_net_map )
344 }
while( !d356_net_set.insert( canon ).second );
347 fprintf( aFile,
"P NNAME%-5.5s %-66.66s\n",
TO_UTF8( canon ),
352 [&](
const D356_RECORD& aRecord ) ->
const wxString&
354 return aRecord.netname.empty() ? nc : d356_net_map.at( aRecord.netname );
358 std::stable_sort( aRecords.begin(), aRecords.end(),
361 return signalName( aLhs ) < signalName( aRhs );
366 const wxString& d356_net = signalName( rk );
367 const bool blindOrBuried = rk.start_layer != 0;
374 else if( rk.mechanical )
376 else if( blindOrBuried )
381 auto writeComponent =
382 [&](
int aOpCode,
bool aMidpoint )
384 fprintf( aFile,
"%03d%-14.14s %-6.6s%c%-4.4s%c", aOpCode,
TO_UTF8( d356_net ),
385 TO_UTF8( rk.refdes ), rk.pin.empty() ?
' ' :
'-',
TO_UTF8( rk.pin ),
386 aMidpoint ?
'M' :
' ' );
389 auto writeAccessAndSize =
392 fprintf( aFile,
"A%02dX%+07dY%+07dX%04dY%04dR%03d", rk.access,
397 writeComponent( rktype, rk.midpoint );
402 fprintf( aFile,
"D%04d%c",
404 rk.mechanical ?
'U':
'P' );
408 fprintf( aFile,
" " );
414 fprintf( aFile,
"A%02dX%+07dY%+07d%14s", rk.access,
iu_to_d356( rk.x_location, 999999 ),
419 writeAccessAndSize();
423 fprintf( aFile,
" S%d", rk.soldermask );
427 fprintf( aFile,
"L%02dL%02d\n", rk.start_layer, rk.end_layer );
429 fprintf( aFile,
"%6s\n",
"" );
433 && ( rk.start_layer == 1 || rk.end_layer ==
m_pcb->GetCopperLayerCount() ) )
436 writeComponent( 27,
false );
437 fprintf( aFile,
" " );
438 writeAccessAndSize();
439 fprintf( aFile,
" S%d%6s\n", rk.soldermask,
"" );
447 FILE* file =
nullptr;
450 if( ( file = wxFopen( aFilename, wxT(
"wt" ) ) ) ==
nullptr )
456 std::vector<D356_RECORD> d356_records;
466 [&](
const std::string& aRecord )
468 fprintf( file,
"%-80.80s\n", aRecord.substr( 0, 72 ).c_str() );
472 writeRecord(
"P JOB " + std::string(
TO_UTF8( wxFileName(
m_pcb->GetFileName() ).GetName() ) ) );
473 writeRecord(
"P CODE 00" );
474 writeRecord(
"P UNITS CUST 0" );
475 writeRecord(
"P arrayDim N" );
476 writeRecord(
"P VER IPC-D-356A" );
477 writeRecord(
"P IMAGE PRIMARY" );
479 writeRecord(
"999" );
503 customizer.AddStaticText( wxT(
"\n\n" ) );
506 m_cb = customizer.AddCheckBox(
_(
"Do not export unconnected pads" ) );
527 wxFileName fn =
m_frame->GetBoard()->GetFileName();
528 wxString ext, wildcard;
534 wxString pro_dir = wxPathOnly(
m_frame->Prj().GetProjectFullName() );
536 wxFileDialog dlg(
m_frame,
_(
"Generate IPC-D-356 netlist file" ), pro_dir, fn.GetFullName(),
537 wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
539 dlg.SetCustomizeHook( customizeHook );
543 if( dlg.ShowModal() == wxID_CANCEL )
549 m_frame->GetPcbNewSettings()->m_ExportD356.doNotExportUnconnectedPads = doNotExportUnconnectedPads;
551 if( writer.
Write( dlg.GetPath() ) )
constexpr EDA_IU_SCALE pcbIUScale
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
const VECTOR2I & GetAuxOrigin() const
int GenD356File(const TOOL_EVENT &aEvent)
Information pertinent to a Pcbnew printed circuit board.
int GetCopperLayerCount() const
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
virtual void TransferDataFromCustomControls() override
D365_CUSTOMIZE_HOOK(bool aDoNotExportUnconnectedPads=false)
wxDECLARE_NO_COPY_CLASS(D365_CUSTOMIZE_HOOK)
wxFileDialogCheckBox * m_cb
bool GetDoNotExportUnconnectedPads() const
bool m_doNotExportUnconnectedPads
virtual void AddCustomControls(wxFileDialogCustomize &customizer) override
Wrapper to expose an API for writing IPC-D356 files.
bool m_doNotExportUnconnectedPads
void write_D356_records(std::vector< D356_RECORD > &aRecords, FILE *aFile)
Writes a list of records to the given output stream.
void SetDoNotExportUnconnectedPads(bool aDoNotExportUnconnectedPads)
Sets whether unconnected pads should be exported.
void appendPadRecords(BOARD *aPcb, const std::vector< FAB_TEST_POINT > &aPoints, std::vector< D356_RECORD > &aRecords)
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.
LSET is a set of PCB_LAYER_IDs.
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Handle the data for a net.
const wxString & GetNetname() const
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
@ NORMAL
Shape is the same on all layers.
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
This file is part of the common library.
static int physical_layer(BOARD *aPcb, PCB_LAYER_ID aLayer)
static void appendViaRecords(BOARD *aPcb, const std::vector< FAB_TEST_POINT > &aPoints, std::vector< D356_RECORD > &aRecords)
static wxString sanitize_d356_name(const wxString &aNetname, bool aUpper)
static wxString d356_alias(int aIndex)
static int iu_to_d356(int iu, int clamp)
static int via_access_code(BOARD *aPcb, int top_layer, int bottom_layer)
static int compute_pad_access_code(BOARD *aPcb, LSET aLayerMask)
std::vector< FAB_TEST_POINT > FabTestPoints(const BOARD &aBoard)
The caller must build board connectivity before enumerating net endpoints.
static const std::string IpcD356FileExtension
static wxString IpcD356FileWildcard()
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
size_t CopperLayerToOrdinal(PCB_LAYER_ID aLayer)
Converts KiCad copper layer enum to an ordinal between the front and back layers.
PCB_LAYER_ID
A quick note on layer IDs:
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...
VECTOR2< int32_t > VECTOR2I
Definition of file extensions used in Kicad.