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 <qa_utils/file_utils.h>
39#include <boost/test/unit_test.hpp>
40
43#include <base_units.h>
44#include <board.h>
45#include <filename_resolver.h>
46#include <footprint.h>
47#include <geometry/eda_angle.h>
49#include <pcb_shape.h>
50#include <reporter.h>
51
52#include <wx/string.h>
53
54
55namespace
56{
57
61std::map<int, std::vector<VECTOR2D>> ReadBoardOutlineLoops( const std::filesystem::path& aEmnPath )
62{
63 std::ifstream in( aEmnPath );
64 BOOST_REQUIRE_MESSAGE( in, "Cannot open exported .emn: " << aEmnPath.string() );
65
66 std::map<int, std::vector<VECTOR2D>> loops;
67 std::string line;
68 bool inSection = false;
69
70 while( std::getline( in, line ) )
71 {
72 if( line.find( ".BOARD_OUTLINE" ) != std::string::npos )
73 {
74 inSection = true;
75 continue;
76 }
77
78 if( line.find( ".END_BOARD_OUTLINE" ) != std::string::npos )
79 break;
80
81 if( !inSection )
82 continue;
83
84 std::istringstream ss( line );
85 std::vector<std::string> tokens;
86
87 for( std::string tok; ss >> tok; )
88 tokens.push_back( tok );
89
90 // A point record is "<loopIndex> <x> <y> <angle>"; skip the owner line and the single-value
91 // thickness header, which would otherwise be miscounted as a loop index.
92 if( tokens.size() < 4 )
93 continue;
94
95 const std::string& first = tokens.front();
96 const bool isLoopIndex =
97 std::all_of( first.begin(), first.end(), []( unsigned char c ) { return std::isdigit( c ); } );
98
99 if( isLoopIndex )
100 {
101 loops[std::stoi( first )].emplace_back( std::stod( tokens[1] ),
102 std::stod( tokens[2] ) );
103 }
104 }
105
106 return loops;
107}
108
109
113void CheckLoopContainsPoints( const std::vector<VECTOR2D>& aLoopPoints,
114 const std::vector<VECTOR2I>& aExpectedIU )
115{
116 for( const VECTOR2I& expected : aExpectedIU )
117 {
118 const double expX = expected.x * pcbIUScale.MM_PER_IU;
119 const double expY = -expected.y * pcbIUScale.MM_PER_IU;
120
121 const bool found = std::any_of( aLoopPoints.begin(), aLoopPoints.end(),
122 [&]( const VECTOR2D& pt )
123 {
124 return std::abs( pt.x - expX ) < 1e-3
125 && std::abs( pt.y - expY ) < 1e-3;
126 } );
127
128 BOOST_CHECK_MESSAGE( found, "Cutout corner (" << expX << ", " << expY
129 << ") missing from exported loop" );
130 }
131}
132
133
135PCB_SHAPE* GetCutoutShape( FOOTPRINT* aFootprint )
136{
137 PCB_SHAPE* cutout = nullptr;
138
139 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
140 {
141 if( item->Type() != PCB_SHAPE_T || item->GetLayer() != Edge_Cuts )
142 continue;
143
144 BOOST_REQUIRE( !cutout );
145 cutout = static_cast<PCB_SHAPE*>( item );
146 }
147
148 BOOST_REQUIRE( cutout );
149 return cutout;
150}
151
152} // namespace
153
154
155BOOST_AUTO_TEST_SUITE( IdfExport )
156
157
158BOOST_AUTO_TEST_CASE( FootprintCutoutIsExported )
159{
160 const std::string sourceBoard = KI_TEST::GetPcbnewTestDataDir() + "issue5854.kicad_pcb";
161 BOOST_REQUIRE_MESSAGE( std::filesystem::exists( sourceBoard ), "Missing test board " << sourceBoard );
162
163 std::unique_ptr<BOARD> board = KI_TEST::RequireBoardFromPcbnewQAData( "issue5854.kicad_pcb" );
164
165 KI_TEST::SCOPED_TEMP_DIR outDir( wxS( "kicad_idf_cutout_test" ) );
166
167 const std::filesystem::path emnPath = outDir.Path() / "cutout.emn";
168
171 FILENAME_RESOLVER resolver;
173
174 IDF_EXPORTER exporter( board.get(), &resolver, &job, &reporter );
175
176 const bool ok = exporter.Export( wxString::FromUTF8( emnPath.string().c_str() ) );
177
178 BOOST_REQUIRE_MESSAGE( ok, "IDF export failed: " << reporter.GetMessages() );
179 BOOST_REQUIRE( std::filesystem::exists( emnPath ) );
180
181 const std::map<int, std::vector<VECTOR2D>> loops = ReadBoardOutlineLoops( emnPath );
182
183 // The board perimeter is loop 0; the footprint cutout must add at least one further loop.
184 BOOST_CHECK_MESSAGE( loops.count( 0 ) == 1, "Board outline (loop 0) missing from .emn" );
185 BOOST_REQUIRE_MESSAGE( loops.count( 1 ) == 1, "Footprint cutout (loop 1) missing from .emn" );
186
187 // The cutout rect's corners must land where the board says they are (mm, Y mirrored),
188 // so a reflected or displaced cutout fails even though a loop 1 exists.
189 PCB_SHAPE* cutout = GetCutoutShape( board->Footprints().front() );
191
192 const VECTOR2I start = cutout->GetStart();
193 const VECTOR2I end = cutout->GetEnd();
194
195 CheckLoopContainsPoints( loops.at( 1 ),
196 { start, VECTOR2I( end.x, start.y ), end,
197 VECTOR2I( start.x, end.y ) } );
198}
199
200
201// Rotating the footprint by a non-cardinal angle converts its fp_rect cutout to SHAPE_T::POLY
202// (EDA_SHAPE::rotate), exercising the polygon branch of the cutout emitter.
203BOOST_AUTO_TEST_CASE( RotatedPolygonCutoutIsExported )
204{
205 const std::string sourceBoard = KI_TEST::GetPcbnewTestDataDir() + "issue5854.kicad_pcb";
206 BOOST_REQUIRE_MESSAGE( std::filesystem::exists( sourceBoard ), "Missing test board " << sourceBoard );
207
208 std::unique_ptr<BOARD> board = KI_TEST::RequireBoardFromPcbnewQAData( "issue5854.kicad_pcb" );
209 BOOST_REQUIRE( !board->Footprints().empty() );
210
211 FOOTPRINT* footprint = board->Footprints().front();
212 footprint->Rotate( footprint->GetPosition(), EDA_ANGLE( 45.0, DEGREES_T ) );
213
214 KI_TEST::SCOPED_TEMP_DIR outDir( wxS( "kicad_idf_poly_cutout_test" ) );
215
216 const std::filesystem::path emnPath = outDir.Path() / "poly_cutout.emn";
217
220 FILENAME_RESOLVER resolver;
222
223 IDF_EXPORTER exporter( board.get(), &resolver, &job, &reporter );
224
225 const bool ok = exporter.Export( wxString::FromUTF8( emnPath.string().c_str() ) );
226
227 BOOST_REQUIRE_MESSAGE( ok, "IDF export failed: " << reporter.GetMessages() );
228 BOOST_REQUIRE( std::filesystem::exists( emnPath ) );
229
230 const std::map<int, std::vector<VECTOR2D>> loops = ReadBoardOutlineLoops( emnPath );
231
232 BOOST_CHECK_MESSAGE( loops.count( 0 ) == 1, "Board outline (loop 0) missing from .emn" );
233 BOOST_REQUIRE_MESSAGE( loops.count( 1 ) == 1,
234 "Rotated polygon cutout (loop 1) missing from .emn" );
235
236 // The rotated cutout is now a POLY; its outline corners must appear in the exported loop
237 PCB_SHAPE* cutout = GetCutoutShape( footprint );
238 BOOST_REQUIRE( cutout->GetShape() == SHAPE_T::POLY );
239
240 std::vector<VECTOR2I> corners;
241 const SHAPE_LINE_CHAIN& chain = cutout->GetPolyShape().COutline( 0 );
242
243 for( int ii = 0; ii < chain.PointCount(); ++ii )
244 corners.push_back( chain.CPoint( ii ) );
245
246 CheckLoopContainsPoints( loops.at( 1 ), corners );
247}
248
249
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:85
SHAPE_POLY_SET & GetPolyShape()
SHAPE_T GetShape() const
Definition eda_shape.h:177
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:333
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:283
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:379
DRAWINGS & GraphicalItems()
Definition footprint.h:358
bool Export(const wxString &aFullFileName) const
Generate IDFv3 compliant board (*.emn) and library (*.emp) files for aPcb.
IDF_SETTINGS::COORD_ORIGIN m_originMode
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
Definition file_utils.h:61
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 > RequireBoardFromPcbnewQAData(const std::filesystem::path &aFilename)
Require a board from the path relative to the pcbnew QA data directory.
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