KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_d356_export.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21
22#include <algorithm>
23#include <filesystem>
24#include <fstream>
25#include <memory>
26#include <string>
27#include <vector>
28
29#include <board.h>
31#include <pcb_track.h>
33
34#include <wx/string.h>
35
36
37namespace
38{
39std::string trim( const std::string& aStr )
40{
41 const size_t begin = aStr.find_first_not_of( ' ' );
42
43 if( begin == std::string::npos )
44 return std::string();
45
46 const size_t end = aStr.find_last_not_of( ' ' );
47
48 return aStr.substr( begin, end - begin + 1 );
49}
50
51
52std::vector<std::string> exportD356( const std::string& aBoardFile )
53{
54 std::unique_ptr<BOARD> board = KI_TEST::ReadBoardFromFileOrStream(
55 ( std::filesystem::path( KI_TEST::GetPcbnewTestDataDir() ) / aBoardFile ).string() );
56 BOOST_REQUIRE( board );
57
58 const std::filesystem::path outputPath = std::filesystem::temp_directory_path() / ( aBoardFile + ".d356" );
59
60 IPC356D_WRITER writer( board.get() );
61 BOOST_REQUIRE( writer.Write( wxString::FromUTF8( outputPath.string().c_str() ) ) );
62
63 std::ifstream in( outputPath );
64 std::vector<std::string> lines;
65
66 for( std::string line; std::getline( in, line ); )
67 lines.push_back( line );
68
69 in.close();
70 std::filesystem::remove( outputPath );
71 return lines;
72}
73} // namespace
74
75
76/*
77 * IPC-D-356A column 32 carries the midpoint flag ('M' = a mid-net access point,
78 * blank = an end-net point / component terminal). Vias and unnamed copper features
79 * are mid-net; named component pins are end-net. The fixture exercises all three.
80 */
81BOOST_AUTO_TEST_CASE( ExportD356MidpointFlag )
82{
83 const std::filesystem::path boardPath =
84 std::filesystem::path( KI_TEST::GetPcbnewTestDataDir() ) / "issue3812.kicad_pcb";
85
86 BOOST_REQUIRE( std::filesystem::exists( boardPath ) );
87
88 std::unique_ptr<BOARD> board = KI_TEST::ReadBoardFromFileOrStream( boardPath.string() );
89 BOOST_REQUIRE( board );
90
91 const std::filesystem::path outputPath =
92 std::filesystem::temp_directory_path() / "kicad_d356_midpoint_test.d356";
93
94 IPC356D_WRITER writer( board.get() );
95 BOOST_REQUIRE( writer.Write( wxString::FromUTF8( outputPath.string().c_str() ) ) );
96
97 std::ifstream in( outputPath );
98 BOOST_REQUIRE( in.is_open() );
99
100 // The soldermask field encodes which sides are tented; derive the expectation from the
101 // board's vias (the fixture tents them uniformly) rather than hardcoding a value.
102 int expectedViaMask = -1;
103
104 for( PCB_TRACK* track : board->Tracks() )
105 {
106 if( track->Type() != PCB_VIA_T )
107 continue;
108
109 PCB_VIA* via = static_cast<PCB_VIA*>( track );
110 const int mask = ( via->IsTented( F_Mask ) ? 1 : 0 ) | ( via->IsTented( B_Mask ) ? 2 : 0 );
111
112 if( expectedViaMask < 0 )
113 expectedViaMask = mask;
114 else
115 BOOST_REQUIRE_EQUAL( mask, expectedViaMask );
116 }
117
118 std::string line;
119 int namedPads = 0;
120 int unnamedPads = 0;
121 int vias = 0;
122
123 while( std::getline( in, line ) )
124 {
125 // Only 3xx test-point records carry the midpoint flag in column 32
126 if( line.size() < 32 || line[0] != '3' )
127 continue;
128
129 const std::string refdes = trim( line.substr( 20, 6 ) );
130 const std::string pin = trim( line.substr( 27, 4 ) );
131 const char midpoint = line[31];
132
133 if( refdes == "VIA" )
134 {
135 ++vias;
136 BOOST_CHECK_EQUAL( midpoint, 'M' );
137
138 // 'S' cannot occur in the fixed-format fields after column 32, so the last one
139 // starts the soldermask field
140 const size_t sPos = line.rfind( 'S' );
141 BOOST_REQUIRE( sPos != std::string::npos && sPos > 31 );
142 BOOST_CHECK_EQUAL( std::stoi( line.substr( sPos + 1 ) ), expectedViaMask );
143 }
144 else if( pin.empty() )
145 {
146 ++unnamedPads;
147 BOOST_CHECK_EQUAL( midpoint, 'M' );
148 }
149 else
150 {
151 ++namedPads;
152 BOOST_CHECK_EQUAL( midpoint, ' ' );
153 }
154 }
155
156 // Guard against a silently trivial pass by requiring each record class
157 BOOST_CHECK_GT( namedPads, 0 );
158 BOOST_CHECK_GT( unnamedPads, 0 );
159 BOOST_CHECK_GT( vias, 0 );
160
161 in.close();
162 std::filesystem::remove( outputPath );
163}
164
165
166// A blind via is a 307 record with its layer span in columns 75-80 plus a 027 surface pad record
167// The fixture's blind vias run from F.Cu to In2.Cu
168BOOST_AUTO_TEST_CASE( ExportD356BlindVias )
169{
170 const std::vector<std::string> lines = exportD356( "issue10697.kicad_pcb" );
171
172 BOOST_CHECK( std::any_of( lines.begin(), lines.end(),
173 []( const std::string& aLine )
174 {
175 return aLine.rfind( "P VER IPC-D-356A ", 0 ) == 0;
176 } ) );
177
178 int blindVias = 0;
179
180 for( size_t ii = 0; ii < lines.size(); ++ii )
181 {
182 if( lines[ii].rfind( "307", 0 ) != 0 )
183 continue;
184
185 ++blindVias;
186 BOOST_REQUIRE_EQUAL( lines[ii].size(), 80u );
187 BOOST_CHECK_EQUAL( lines[ii].substr( 38, 3 ), "A01" );
188 BOOST_CHECK_EQUAL( lines[ii].substr( 71, 2 ), " S" );
189 BOOST_CHECK_EQUAL( lines[ii].substr( 74 ), "L01L03" );
190
191 // 0.3175 mm surface pad is 125 decimils
192 BOOST_REQUIRE_LT( ii + 1, lines.size() );
193 BOOST_REQUIRE_EQUAL( lines[ii + 1].size(), 80u );
194 BOOST_CHECK_EQUAL( lines[ii + 1].substr( 0, 17 ), "027" + lines[ii].substr( 3, 14 ) );
195 BOOST_CHECK_EQUAL( lines[ii + 1].substr( 38, 3 ), "A01" );
196 BOOST_CHECK_EQUAL( lines[ii + 1].substr( 57, 5 ), "X0125" );
197 }
198
199 BOOST_CHECK_EQUAL( blindVias, 7 );
200
201 // Records are sorted by net, and names past 14 characters go through an NNAME alias
202 const std::vector<std::string> longNames = exportD356( "issue3812.kicad_pcb" );
203 std::vector<std::string> nets;
204 std::string alias;
205
206 for( const std::string& line : longNames )
207 {
208 if( line[0] == '3' )
209 nets.push_back( line.substr( 3, 14 ) );
210
211 if( line.rfind( "P NNAME", 0 ) == 0 && trim( line.substr( 14 ) ) == "/inout_user/CAN_H" )
212 alias = line.substr( 8, 5 );
213 }
214
215 BOOST_CHECK( std::is_sorted( nets.begin(), nets.end() ) );
216
217 BOOST_REQUIRE_EQUAL( alias.size(), 5u );
218 BOOST_CHECK( std::any_of( longNames.begin(), longNames.end(),
219 [&]( const std::string& aLine )
220 {
221 return aLine[0] == '3' && trim( aLine.substr( 3, 14 ) ) == alias;
222 } ) );
223}
General utilities for PCB file IO for QA programs.
Wrapper to expose an API for writing IPC-D356 files.
Definition export_d356.h:60
bool Write(const wxString &aFilename)
Generates and writes the netlist to a given path.
@ B_Mask
Definition layer_ids.h:94
@ F_Mask
Definition layer_ids.h:93
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
std::unique_ptr< BOARD > ReadBoardFromFileOrStream(const std::string &aFilename, std::istream &aFallback)
Read a board from a file, or another stream, as appropriate.
BOOST_AUTO_TEST_CASE(ExportD356MidpointFlag)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
KIBIS_PIN * pin
VECTOR2I end
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89