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::RequireBoardFromPcbnewQAData( aBoardFile );
55
56 const std::filesystem::path outputPath = std::filesystem::temp_directory_path() / ( aBoardFile + ".d356" );
57
58 IPC356D_WRITER writer( board.get() );
59 BOOST_REQUIRE( writer.Write( wxString::FromUTF8( outputPath.string().c_str() ) ) );
60
61 std::ifstream in( outputPath );
62 std::vector<std::string> lines;
63
64 for( std::string line; std::getline( in, line ); )
65 lines.push_back( line );
66
67 in.close();
68 std::filesystem::remove( outputPath );
69 return lines;
70}
71} // namespace
72
73
74/*
75 * IPC-D-356A column 32 carries the midpoint flag ('M' = a mid-net access point,
76 * blank = an end-net point / component terminal). Vias and unnamed copper features
77 * are mid-net; named component pins are end-net. The fixture exercises all three.
78 */
79BOOST_AUTO_TEST_CASE( ExportD356MidpointFlag )
80{
81 const std::filesystem::path boardPath =
82 std::filesystem::path( KI_TEST::GetPcbnewTestDataDir() ) / "issue3812.kicad_pcb";
83
84 BOOST_REQUIRE( std::filesystem::exists( boardPath ) );
85
86 std::unique_ptr<BOARD> board = KI_TEST::RequireBoardFromPcbnewQAData( "issue3812.kicad_pcb" );
87
88 const std::filesystem::path outputPath =
89 std::filesystem::temp_directory_path() / "kicad_d356_midpoint_test.d356";
90
91 IPC356D_WRITER writer( board.get() );
92 BOOST_REQUIRE( writer.Write( wxString::FromUTF8( outputPath.string().c_str() ) ) );
93
94 std::ifstream in( outputPath );
95 BOOST_REQUIRE( in.is_open() );
96
97 // The soldermask field encodes which sides are tented; derive the expectation from the
98 // board's vias (the fixture tents them uniformly) rather than hardcoding a value.
99 int expectedViaMask = -1;
100
101 for( PCB_TRACK* track : board->Tracks() )
102 {
103 if( track->Type() != PCB_VIA_T )
104 continue;
105
106 PCB_VIA* via = static_cast<PCB_VIA*>( track );
107 const int mask = ( via->IsTented( F_Mask ) ? 1 : 0 ) | ( via->IsTented( B_Mask ) ? 2 : 0 );
108
109 if( expectedViaMask < 0 )
110 expectedViaMask = mask;
111 else
112 BOOST_REQUIRE_EQUAL( mask, expectedViaMask );
113 }
114
115 std::string line;
116 int namedPads = 0;
117 int unnamedPads = 0;
118 int vias = 0;
119
120 while( std::getline( in, line ) )
121 {
122 // Only 3xx test-point records carry the midpoint flag in column 32
123 if( line.size() < 32 || line[0] != '3' )
124 continue;
125
126 const std::string refdes = trim( line.substr( 20, 6 ) );
127 const std::string pin = trim( line.substr( 27, 4 ) );
128 const char midpoint = line[31];
129
130 if( refdes == "VIA" )
131 {
132 ++vias;
133 BOOST_CHECK_EQUAL( midpoint, 'M' );
134
135 // 'S' cannot occur in the fixed-format fields after column 32, so the last one
136 // starts the soldermask field
137 const size_t sPos = line.rfind( 'S' );
138 BOOST_REQUIRE( sPos != std::string::npos && sPos > 31 );
139 BOOST_CHECK_EQUAL( std::stoi( line.substr( sPos + 1 ) ), expectedViaMask );
140 }
141 else if( pin.empty() )
142 {
143 ++unnamedPads;
144 BOOST_CHECK_EQUAL( midpoint, 'M' );
145 }
146 else
147 {
148 ++namedPads;
149 BOOST_CHECK_EQUAL( midpoint, ' ' );
150 }
151 }
152
153 // Guard against a silently trivial pass by requiring each record class
154 BOOST_CHECK_GT( namedPads, 0 );
155 BOOST_CHECK_GT( unnamedPads, 0 );
156 BOOST_CHECK_GT( vias, 0 );
157
158 in.close();
159 std::filesystem::remove( outputPath );
160}
161
162
163// A blind via is a 307 record with its layer span in columns 75-80 plus a 027 surface pad record
164// The fixture's blind vias run from F.Cu to In2.Cu
165BOOST_AUTO_TEST_CASE( ExportD356BlindVias )
166{
167 const std::vector<std::string> lines = exportD356( "issue10697.kicad_pcb" );
168
169 BOOST_CHECK( std::any_of( lines.begin(), lines.end(),
170 []( const std::string& aLine )
171 {
172 return aLine.rfind( "P VER IPC-D-356A ", 0 ) == 0;
173 } ) );
174
175 int blindVias = 0;
176
177 for( size_t ii = 0; ii < lines.size(); ++ii )
178 {
179 if( lines[ii].rfind( "307", 0 ) != 0 )
180 continue;
181
182 ++blindVias;
183 BOOST_REQUIRE_EQUAL( lines[ii].size(), 80u );
184 BOOST_CHECK_EQUAL( lines[ii].substr( 38, 3 ), "A01" );
185 BOOST_CHECK_EQUAL( lines[ii].substr( 71, 2 ), " S" );
186 BOOST_CHECK_EQUAL( lines[ii].substr( 74 ), "L01L03" );
187
188 // 0.3175 mm surface pad is 125 decimils
189 BOOST_REQUIRE_LT( ii + 1, lines.size() );
190 BOOST_REQUIRE_EQUAL( lines[ii + 1].size(), 80u );
191 BOOST_CHECK_EQUAL( lines[ii + 1].substr( 0, 17 ), "027" + lines[ii].substr( 3, 14 ) );
192 BOOST_CHECK_EQUAL( lines[ii + 1].substr( 38, 3 ), "A01" );
193 BOOST_CHECK_EQUAL( lines[ii + 1].substr( 57, 5 ), "X0125" );
194 }
195
196 BOOST_CHECK_EQUAL( blindVias, 7 );
197
198 // Records are sorted by net, and names past 14 characters go through an NNAME alias
199 const std::vector<std::string> longNames = exportD356( "issue3812.kicad_pcb" );
200 std::vector<std::string> nets;
201 std::string alias;
202
203 for( const std::string& line : longNames )
204 {
205 if( line[0] == '3' )
206 nets.push_back( line.substr( 3, 14 ) );
207
208 if( line.rfind( "P NNAME", 0 ) == 0 && trim( line.substr( 14 ) ) == "/inout_user/CAN_H" )
209 alias = line.substr( 8, 5 );
210 }
211
212 BOOST_CHECK( std::is_sorted( nets.begin(), nets.end() ) );
213
214 BOOST_REQUIRE_EQUAL( alias.size(), 5u );
215 BOOST_CHECK( std::any_of( longNames.begin(), longNames.end(),
216 [&]( const std::string& aLine )
217 {
218 return aLine[0] == '3' && trim( aLine.substr( 3, 14 ) ) == alias;
219 } ) );
220}
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 > RequireBoardFromPcbnewQAData(const std::filesystem::path &aFilename)
Require a board from the path relative to the pcbnew QA data directory.
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