KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_odb_soldermask_shape_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 <filesystem>
21#include <fstream>
22#include <memory>
23
25#include <boost/test/unit_test.hpp>
26
27#include <base_units.h>
28#include <board.h>
29#include <footprint.h>
30#include <kiid.h>
31#include <pcb_shape.h>
34#include <core/utf8.h>
35
36namespace fs = std::filesystem;
37
44static int countOdbSurfaceRecords( const fs::path& aFeaturesFile )
45{
46 int surfaceCount = 0;
47 std::ifstream stream( aFeaturesFile );
48 std::string line;
49
50 while( std::getline( stream, line ) )
51 {
52 if( line.rfind( "S ", 0 ) == 0 )
53 surfaceCount++;
54 }
55
56 return surfaceCount;
57}
58
59
72static int countMaskSurfaceRecords( const fs::path& aRoot, bool& aFoundFile )
73{
74 int total = 0;
75 aFoundFile = false;
76
77 for( const fs::directory_entry& entry : fs::recursive_directory_iterator( aRoot ) )
78 {
79 if( !entry.is_regular_file() || entry.path().filename() != "features" )
80 continue;
81
82 const std::string layerDir = entry.path().parent_path().filename().string();
83
84 if( layerDir.find( "mask" ) == std::string::npos )
85 continue;
86
87 aFoundFile = true;
88 total += countOdbSurfaceRecords( entry.path() );
89 }
90
91 return total;
92}
93
94
102BOOST_AUTO_TEST_CASE( OdbSolderMaskShapeExport )
103{
104 SETTINGS_MANAGER settingsManager;
105 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
106
107 const int side = pcbIUScale.mmToIU( 20 );
108
109 // Give the board a square Edge.Cuts outline so the export produces a valid board profile. Edge
110 // cuts land in their own layer feature file, not the mask one we inspect, so they cannot mask a
111 // dropped shape.
112 auto addEdge = [&]( const VECTOR2I& aStart, const VECTOR2I& aEnd )
113 {
114 PCB_SHAPE* edge = new PCB_SHAPE( board.get(), SHAPE_T::SEGMENT );
115 edge->SetLayer( Edge_Cuts );
116 edge->SetStart( aStart );
117 edge->SetEnd( aEnd );
118 edge->SetWidth( pcbIUScale.mmToIU( 0.1 ) );
119 board->Add( edge );
120 };
121
122 addEdge( { 0, 0 }, { side, 0 } );
123 addEdge( { side, 0 }, { side, side } );
124 addEdge( { side, side }, { 0, side } );
125 addEdge( { 0, side }, { 0, 0 } );
126
127 // The item under test: a filled polygon on the front copper layer of a footprint, flagged to
128 // open the solder mask over itself. SetHasSolderMask() is what puts the shape on F_Mask via
129 // PCB_SHAPE::GetLayerSet(), the layer the export must honor.
130 FOOTPRINT* fp = new FOOTPRINT( board.get() );
131 fp->SetReference( wxT( "U1" ) );
132 fp->SetPosition( { pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) } );
133
134 PCB_SHAPE* poly = new PCB_SHAPE( fp, SHAPE_T::POLY );
135 poly->SetLayer( F_Cu );
136 poly->SetFilled( true );
137 poly->SetPolyPoints( { { pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) },
138 { pcbIUScale.mmToIU( 15 ), pcbIUScale.mmToIU( 5 ) },
139 { pcbIUScale.mmToIU( 15 ), pcbIUScale.mmToIU( 15 ) },
140 { pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 15 ) } } );
141 poly->SetHasSolderMask( true );
142
143 BOOST_REQUIRE_MESSAGE( poly->IsOnLayer( F_Mask ),
144 "Test shape is not reported on F_Mask; SetHasSolderMask had no effect" );
145
146 fp->Add( poly );
147 board->Add( fp );
148
149 const fs::path outDir =
150 fs::temp_directory_path() / ( "kicad_qa_odb_soldermask_24690_" + KIID().AsString().ToStdString() );
151
152 BOOST_REQUIRE( fs::create_directory( outDir ) );
153
154 PCB_IO_ODBPP odbExporter;
155 std::map<std::string, UTF8> props;
156 props["units"] = "mm";
157 props["sigfig"] = "6";
158
159 BOOST_REQUIRE_NO_THROW( odbExporter.SaveBoard( outDir.string(), board.get(), &props ) );
160
161 BOOST_REQUIRE_MESSAGE( fs::exists( outDir ), "ODB++ export produced no output tree" );
162
163 bool foundMask = false;
164 int surfaceCount = countMaskSurfaceRecords( outDir, foundMask );
165
166 BOOST_REQUIRE_MESSAGE( foundMask, "ODB++ export produced no solder-mask features file" );
167
168 BOOST_CHECK_MESSAGE( surfaceCount > 0, "Mask-flagged shape was dropped from the ODB++ export "
169 "(no surface features on the solder-mask layer)" );
170
171 fs::remove_all( outDir );
172}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:152
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
Definition kiid.h:44
wxString AsString() const
Definition kiid.cpp:242
void SaveBoard(const wxString &aFileName, BOARD *aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
void SetWidth(int aWidth) override
void SetHasSolderMask(bool aVal)
Definition pcb_shape.h:328
void SetEnd(const VECTOR2I &aEnd) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
@ SEGMENT
Definition eda_shape.h:46
@ Edge_Cuts
Definition layer_ids.h:108
@ F_Mask
Definition layer_ids.h:93
@ F_Cu
Definition layer_ids.h:60
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_CASE(OdbSolderMaskShapeExport)
Regression guard for GitLab #24690: a footprint polygon on a copper layer with the "Solder mask" flag...
static int countOdbSurfaceRecords(const fs::path &aFeaturesFile)
Count surface ("S ") feature records in a single ODB++ "features" file.
static int countMaskSurfaceRecords(const fs::path &aRoot, bool &aFoundFile)
Sum surface ("S ") feature records across every solder-mask "features" file in an ODB++ tree.
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683