KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_idf_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/*
21 * Verifies that Edge_Cuts graphics living inside a footprint are exported as board cutouts in the
22 * IDFv3 .emn output. issue5854.kicad_pcb is a DIP-14 footprint carrying an fp_rect on Edge.Cuts (a
23 * rectangular board cavity) inside a rectangular board outline; the exporter must emit the board
24 * outline as loop 0 and the footprint cutout as loop 1.
25 */
26
27#include <algorithm>
28#include <cctype>
29#include <cmath>
30#include <filesystem>
31#include <fstream>
32#include <map>
33#include <sstream>
34#include <string>
35#include <vector>
36
38#include <boost/test/unit_test.hpp>
39
42#include <base_units.h>
43#include <board.h>
44#include <filename_resolver.h>
45#include <footprint.h>
46#include <geometry/eda_angle.h>
48#include <pcb_shape.h>
49#include <reporter.h>
50
51#include <wx/string.h>
52
53
54namespace
55{
56
60std::map<int, std::vector<VECTOR2D>> ReadBoardOutlineLoops( const std::filesystem::path& aEmnPath )
61{
62 std::ifstream in( aEmnPath );
63 BOOST_REQUIRE_MESSAGE( in, "Cannot open exported .emn: " << aEmnPath.string() );
64
65 std::map<int, std::vector<VECTOR2D>> loops;
66 std::string line;
67 bool inSection = false;
68
69 while( std::getline( in, line ) )
70 {
71 if( line.find( ".BOARD_OUTLINE" ) != std::string::npos )
72 {
73 inSection = true;
74 continue;
75 }
76
77 if( line.find( ".END_BOARD_OUTLINE" ) != std::string::npos )
78 break;
79
80 if( !inSection )
81 continue;
82
83 std::istringstream ss( line );
84 std::vector<std::string> tokens;
85
86 for( std::string tok; ss >> tok; )
87 tokens.push_back( tok );
88
89 // A point record is "<loopIndex> <x> <y> <angle>"; skip the owner line and the single-value
90 // thickness header, which would otherwise be miscounted as a loop index.
91 if( tokens.size() < 4 )
92 continue;
93
94 const std::string& first = tokens.front();
95 const bool isLoopIndex =
96 std::all_of( first.begin(), first.end(), []( unsigned char c ) { return std::isdigit( c ); } );
97
98 if( isLoopIndex )
99 {
100 loops[std::stoi( first )].emplace_back( std::stod( tokens[1] ),
101 std::stod( tokens[2] ) );
102 }
103 }
104
105 return loops;
106}
107
108
112void CheckLoopContainsPoints( const std::vector<VECTOR2D>& aLoopPoints,
113 const std::vector<VECTOR2I>& aExpectedIU )
114{
115 for( const VECTOR2I& expected : aExpectedIU )
116 {
117 const double expX = expected.x * pcbIUScale.MM_PER_IU;
118 const double expY = -expected.y * pcbIUScale.MM_PER_IU;
119
120 const bool found = std::any_of( aLoopPoints.begin(), aLoopPoints.end(),
121 [&]( const VECTOR2D& pt )
122 {
123 return std::abs( pt.x - expX ) < 1e-3
124 && std::abs( pt.y - expY ) < 1e-3;
125 } );
126
127 BOOST_CHECK_MESSAGE( found, "Cutout corner (" << expX << ", " << expY
128 << ") missing from exported loop" );
129 }
130}
131
132
134PCB_SHAPE* GetCutoutShape( FOOTPRINT* aFootprint )
135{
136 PCB_SHAPE* cutout = nullptr;
137
138 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
139 {
140 if( item->Type() != PCB_SHAPE_T || item->GetLayer() != Edge_Cuts )
141 continue;
142
143 BOOST_REQUIRE( !cutout );
144 cutout = static_cast<PCB_SHAPE*>( item );
145 }
146
147 BOOST_REQUIRE( cutout );
148 return cutout;
149}
150
151} // namespace
152
153
154BOOST_AUTO_TEST_SUITE( IdfExport )
155
156
157BOOST_AUTO_TEST_CASE( FootprintCutoutIsExported )
158{
159 const std::string sourceBoard = KI_TEST::GetPcbnewTestDataDir() + "issue5854.kicad_pcb";
160 BOOST_REQUIRE_MESSAGE( std::filesystem::exists( sourceBoard ), "Missing test board " << sourceBoard );
161
162 std::unique_ptr<BOARD> board = KI_TEST::ReadBoardFromFileOrStream( sourceBoard );
163 BOOST_REQUIRE( board );
164
165 const std::filesystem::path outDir =
166 std::filesystem::temp_directory_path() / "kicad_idf_cutout_test";
167 std::filesystem::create_directories( outDir );
168
169 const std::filesystem::path emnPath = outDir / "cutout.emn";
170 std::filesystem::remove( emnPath );
171
174 FILENAME_RESOLVER resolver;
176
177 IDF_EXPORTER exporter( board.get(), &resolver, &job, &reporter );
178
179 const bool ok = exporter.Export( wxString::FromUTF8( emnPath.string().c_str() ) );
180
181 BOOST_REQUIRE_MESSAGE( ok, "IDF export failed: " << reporter.GetMessages() );
182 BOOST_REQUIRE( std::filesystem::exists( emnPath ) );
183
184 const std::map<int, std::vector<VECTOR2D>> loops = ReadBoardOutlineLoops( emnPath );
185
186 // The board perimeter is loop 0; the footprint cutout must add at least one further loop.
187 BOOST_CHECK_MESSAGE( loops.count( 0 ) == 1, "Board outline (loop 0) missing from .emn" );
188 BOOST_REQUIRE_MESSAGE( loops.count( 1 ) == 1, "Footprint cutout (loop 1) missing from .emn" );
189
190 // The cutout rect's corners must land where the board says they are (mm, Y mirrored),
191 // so a reflected or displaced cutout fails even though a loop 1 exists.
192 PCB_SHAPE* cutout = GetCutoutShape( board->Footprints().front() );
194
195 const VECTOR2I start = cutout->GetStart();
196 const VECTOR2I end = cutout->GetEnd();
197
198 CheckLoopContainsPoints( loops.at( 1 ),
199 { start, VECTOR2I( end.x, start.y ), end,
200 VECTOR2I( start.x, end.y ) } );
201
202 std::filesystem::remove_all( outDir );
203}
204
205
206// Rotating the footprint by a non-cardinal angle converts its fp_rect cutout to SHAPE_T::POLY
207// (EDA_SHAPE::rotate), exercising the polygon branch of the cutout emitter.
208BOOST_AUTO_TEST_CASE( RotatedPolygonCutoutIsExported )
209{
210 const std::string sourceBoard = KI_TEST::GetPcbnewTestDataDir() + "issue5854.kicad_pcb";
211 BOOST_REQUIRE_MESSAGE( std::filesystem::exists( sourceBoard ), "Missing test board " << sourceBoard );
212
213 std::unique_ptr<BOARD> board = KI_TEST::ReadBoardFromFileOrStream( sourceBoard );
214 BOOST_REQUIRE( board );
215 BOOST_REQUIRE( !board->Footprints().empty() );
216
217 FOOTPRINT* footprint = board->Footprints().front();
218 footprint->Rotate( footprint->GetPosition(), EDA_ANGLE( 45.0, DEGREES_T ) );
219
220 const std::filesystem::path outDir =
221 std::filesystem::temp_directory_path() / "kicad_idf_poly_cutout_test";
222 std::filesystem::create_directories( outDir );
223
224 const std::filesystem::path emnPath = outDir / "poly_cutout.emn";
225 std::filesystem::remove( emnPath );
226
229 FILENAME_RESOLVER resolver;
231
232 IDF_EXPORTER exporter( board.get(), &resolver, &job, &reporter );
233
234 const bool ok = exporter.Export( wxString::FromUTF8( emnPath.string().c_str() ) );
235
236 BOOST_REQUIRE_MESSAGE( ok, "IDF export failed: " << reporter.GetMessages() );
237 BOOST_REQUIRE( std::filesystem::exists( emnPath ) );
238
239 const std::map<int, std::vector<VECTOR2D>> loops = ReadBoardOutlineLoops( emnPath );
240
241 BOOST_CHECK_MESSAGE( loops.count( 0 ) == 1, "Board outline (loop 0) missing from .emn" );
242 BOOST_REQUIRE_MESSAGE( loops.count( 1 ) == 1,
243 "Rotated polygon cutout (loop 1) missing from .emn" );
244
245 // The rotated cutout is now a POLY; its outline corners must appear in the exported loop
246 PCB_SHAPE* cutout = GetCutoutShape( footprint );
247 BOOST_REQUIRE( cutout->GetShape() == SHAPE_T::POLY );
248
249 std::vector<VECTOR2I> corners;
250 const SHAPE_LINE_CHAIN& chain = cutout->GetPolyShape().COutline( 0 );
251
252 for( int ii = 0; ii < chain.PointCount(); ++ii )
253 corners.push_back( chain.CPoint( ii ) );
254
255 CheckLoopContainsPoints( loops.at( 1 ), corners );
256
257 std::filesystem::remove_all( outDir );
258}
259
260
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
General utilities for PCB file IO for QA programs.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
SHAPE_POLY_SET & GetPolyShape()
SHAPE_T GetShape() const
Definition eda_shape.h:175
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
Provide an extensible class to resolve 3D model paths.
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
VECTOR2I GetPosition() const override
Definition footprint.h:436
DRAWINGS & GraphicalItems()
Definition footprint.h:408
bool Export(const wxString &aFullFileName) const
Generate IDFv3 compliant board (*.emn) and library (*.emp) files for aPcb.
IDF_SETTINGS::COORD_ORIGIN m_originMode
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
A wrapper for reporting to a wxString object.
Definition reporter.h:242
@ DEGREES_T
Definition eda_angle.h:30
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ Edge_Cuts
Definition layer_ids.h:108
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(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(FootprintCutoutIsExported)
IbisParser parser & reporter
VECTOR3I expected(15, 30, 45)
const SHAPE_LINE_CHAIN chain
VECTOR2I end
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707