KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pdf_output_path.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#include <qa_utils/file_utils.h>
22
23#include <filesystem>
24#include <fstream>
25#include <functional>
26#include <memory>
27
28#include <wx/string.h>
29
30#include <cli/exit_codes.h>
32#include <pcbnew_jobs_handler.h>
34
35
36BOOST_AUTO_TEST_SUITE( PdfOutputPath )
37
38
39BOOST_AUTO_TEST_CASE( ExportPdfSingleDocumentOutputPath )
40{
41 PCBNEW_JOBS_HANDLER handler( nullptr );
42
43 const std::filesystem::path boardPath =
44 std::filesystem::path( KI_TEST::GetPcbnewTestDataDir() ) / "api_kitchen_sink.kicad_pcb";
45
46 BOOST_REQUIRE( std::filesystem::exists( boardPath ) );
47
48 KI_TEST::SCOPED_TEMP_DIR outputRoot( "kicad_pdf_single_doc_test" );
49
50 const std::filesystem::path outputPath = outputRoot.Path() / "Assembly" / "Assembly_output.pdf";
51
52 auto pdfJob = std::make_unique<JOB_EXPORT_PCB_PDF>();
53 pdfJob->m_filename = wxString::FromUTF8( boardPath.string().c_str() );
54 pdfJob->SetConfiguredOutputPath( wxString::FromUTF8( outputPath.string().c_str() ) );
55 pdfJob->m_plotDrawingSheet = false;
56 pdfJob->m_pdfSingle = true;
58 pdfJob->m_plotLayerSequence = LSEQ( { F_Cu } );
59
60 int result = handler.JobExportPdf( pdfJob.get() );
62
63 BOOST_CHECK( std::filesystem::exists( outputPath ) );
64 BOOST_CHECK( std::filesystem::is_regular_file( outputPath ) );
65
66 const std::filesystem::path nestedPdf = outputPath / ( boardPath.stem().string() + ".pdf" );
67 BOOST_CHECK( !std::filesystem::exists( nestedPdf ) );
68}
69
70
71BOOST_AUTO_TEST_CASE( ExportPdfSingleDocumentTrailingDisabledLayers )
72{
73 // 24845: trailing disabled copper layers added a blank page in single-document PDF mode.
74 PCBNEW_JOBS_HANDLER handler( nullptr );
75
76 const std::filesystem::path boardPath =
77 std::filesystem::path( KI_TEST::GetPcbnewTestDataDir() ) / "api_kitchen_sink.kicad_pcb";
78 BOOST_REQUIRE( std::filesystem::exists( boardPath ) );
79
80 KI_TEST::SCOPED_TEMP_DIR outputRoot( "kicad_pdf_trailing_disabled_test" );
81
82 const std::filesystem::path outputPath = outputRoot.Path() / "out.pdf";
83
84 auto pdfJob = std::make_unique<JOB_EXPORT_PCB_PDF>();
85 pdfJob->m_filename = wxString::FromUTF8( boardPath.string().c_str() );
86 pdfJob->SetConfiguredOutputPath( wxString::FromUTF8( outputPath.string().c_str() ) );
87 pdfJob->m_plotDrawingSheet = false;
88 pdfJob->m_pdfSingle = true;
90
91 // api_kitchen_sink is 2-layer, so In1_Cu is disabled and skipped, leaving 2 pages.
92 pdfJob->m_plotLayerSequence = LSEQ( { F_Cu, B_Cu, In1_Cu } );
93
94 int result = handler.JobExportPdf( pdfJob.get() );
96 BOOST_REQUIRE( std::filesystem::exists( outputPath ) );
97
98 // Each page object is written as "/Type /Page\n" (the tree root uses "/Type /Pages\n").
99 std::ifstream in( outputPath, std::ios::binary );
100 std::string pdf( ( std::istreambuf_iterator<char>( in ) ), std::istreambuf_iterator<char>() );
101
102 size_t pageCount = 0;
103
104 for( size_t pos = pdf.find( "/Type /Page\n" ); pos != std::string::npos;
105 pos = pdf.find( "/Type /Page\n", pos + 1 ) )
106 ++pageCount;
107
108 BOOST_CHECK_EQUAL( pageCount, 2u ); // 3 before the fix
109
110 in.close();
111}
112
113
114static std::string plotOneLayerToPdf( const std::string& aBoardFile, PCB_LAYER_ID aLayer, const std::string& aTag,
115 const std::function<void( JOB_EXPORT_PCB_PDF* )>& aConfigure = {} )
116{
117 PCBNEW_JOBS_HANDLER handler( nullptr );
118
119 const std::filesystem::path boardPath = std::filesystem::path( KI_TEST::GetPcbnewTestDataDir() ) / aBoardFile;
120 BOOST_REQUIRE( std::filesystem::exists( boardPath ) );
121
122 KI_TEST::SCOPED_TEMP_DIR outputRoot( aTag );
123
124 const std::filesystem::path outputPath = outputRoot.Path() / "out.pdf";
125
126 auto pdfJob = std::make_unique<JOB_EXPORT_PCB_PDF>();
127 pdfJob->m_filename = wxString::FromUTF8( boardPath.string().c_str() );
128 pdfJob->SetConfiguredOutputPath( wxString::FromUTF8( outputPath.string().c_str() ) );
129 pdfJob->m_plotDrawingSheet = false;
130 pdfJob->m_pdfSingle = true;
132 pdfJob->m_plotLayerSequence = LSEQ( { aLayer } );
133
134 if( aConfigure )
135 aConfigure( pdfJob.get() );
136
137 BOOST_REQUIRE_EQUAL( handler.JobExportPdf( pdfJob.get() ), CLI::EXIT_CODES::OK );
138 BOOST_REQUIRE( std::filesystem::exists( outputPath ) );
139
140 std::string pdf;
141
142 {
143 // Windows cannot remove a directory holding a file that is still open
144 std::ifstream in( outputPath, std::ios::binary );
145 pdf.assign( std::istreambuf_iterator<char>( in ), std::istreambuf_iterator<char>() );
146 }
147
148 return pdf;
149}
150
151
152// component_classes.kicad_pcb carries R1 and C2 on the back and U1 on the front.
153// The bookmark titles live in the uncompressed outline dictionary.
154BOOST_AUTO_TEST_CASE( ExportPdfFrontPageBookmarksOnlyFrontFootprints )
155{
156 std::string pdf = plotOneLayerToPdf( "component_classes.kicad_pcb", F_Fab, "kicad_pdf_popup_front_test" );
157
158 BOOST_CHECK( pdf.find( "/Title (U1)" ) != std::string::npos );
159 BOOST_CHECK_MESSAGE( pdf.find( "/Title (R1)" ) == std::string::npos,
160 "back footprint R1 was bookmarked on a front layer page" );
161 BOOST_CHECK_MESSAGE( pdf.find( "/Title (C2)" ) == std::string::npos,
162 "back footprint C2 was bookmarked on a front layer page" );
163}
164
165
166BOOST_AUTO_TEST_CASE( ExportPdfBackPageBookmarksOnlyBackFootprints )
167{
168 std::string pdf = plotOneLayerToPdf( "component_classes.kicad_pcb", B_Fab, "kicad_pdf_popup_back_test" );
169
170 BOOST_CHECK( pdf.find( "/Title (R1)" ) != std::string::npos );
171 BOOST_CHECK( pdf.find( "/Title (C2)" ) != std::string::npos );
172 BOOST_CHECK_MESSAGE( pdf.find( "/Title (U1)" ) == std::string::npos,
173 "front footprint U1 was bookmarked on a back layer page" );
174}
175
176
177// No footprint in component_classes.kicad_pcb has board outline geometry of its own.
178BOOST_AUTO_TEST_CASE( ExportPdfBoardOutlinePageBookmarksNoFootprints )
179{
180 std::string pdf = plotOneLayerToPdf( "component_classes.kicad_pcb", Edge_Cuts, "kicad_pdf_popup_edge_test" );
181
182 BOOST_CHECK_MESSAGE( pdf.find( "/Title (U1)" ) == std::string::npos,
183 "U1 was bookmarked on the board outline page" );
184 BOOST_CHECK_MESSAGE( pdf.find( "/Title (R1)" ) == std::string::npos,
185 "R1 was bookmarked on the board outline page" );
186}
187
188
189// variant_test.kicad_pcb marks R3 do not populate in "Variant A" only, and R1 is always
190// populated. Hiding do not populate footprints leaves nothing of R3 on the fabrication layer.
191BOOST_AUTO_TEST_CASE( ExportPdfFabPageSkipsHiddenDoNotPopulateFootprints )
192{
193 auto hideDNP = []( JOB_EXPORT_PCB_PDF* aJob )
194 {
195 aJob->m_variant = wxT( "Variant A" );
196 aJob->m_hideDNPFPsOnFabLayers = true;
197 };
198
199 std::string pdf =
200 plotOneLayerToPdf( "variant_test/variant_test.kicad_pcb", F_Fab, "kicad_pdf_popup_dnp_test", hideDNP );
201
202 BOOST_CHECK( pdf.find( "/Title (R1)" ) != std::string::npos );
203 BOOST_CHECK_MESSAGE( pdf.find( "/Title (R3)" ) == std::string::npos,
204 "a hidden do not populate footprint was bookmarked on the fab page" );
205}
206
207
208// 3Rs_bv.kicad_pcb is four layers. R3 has through-hole pads, R1 is surface mount, and
209// REF** is an unplated mounting hole, which plots no copper at all.
210BOOST_AUTO_TEST_CASE( ExportPdfInnerLayerPageSkipsSurfaceMountFootprints )
211{
212 std::string pdf = plotOneLayerToPdf( "issue23451/3Rs_bv.kicad_pcb", In1_Cu, "kicad_pdf_popup_inner_test" );
213
214 BOOST_CHECK( pdf.find( "/Title (R3)" ) != std::string::npos );
215 BOOST_CHECK_MESSAGE( pdf.find( "/Title (R1)" ) == std::string::npos,
216 "surface mount R1 was bookmarked on an inner copper page" );
217 BOOST_CHECK_MESSAGE( pdf.find( "/Title (REF**)" ) == std::string::npos,
218 "an unplated hole was bookmarked on an inner copper page" );
219}
220
General utilities for PCB file IO for QA programs.
@ ONE_PAGE_PER_LAYER_ONE_FILE
"Single Document" mode
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
Definition file_utils.h:61
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
@ B_Cu
Definition layer_ids.h:61
@ F_Fab
Definition layer_ids.h:115
@ In1_Cu
Definition layer_ids.h:62
@ F_Cu
Definition layer_ids.h:60
@ B_Fab
Definition layer_ids.h:114
static const int OK
Definition exit_codes.h:30
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
static std::string plotOneLayerToPdf(const std::string &aBoardFile, PCB_LAYER_ID aLayer, const std::string &aTag, const std::function< void(JOB_EXPORT_PCB_PDF *)> &aConfigure={})
BOOST_AUTO_TEST_CASE(ExportPdfSingleDocumentOutputPath)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")