KiCad PCB EDA Suite
Loading...
Searching...
No Matches
gerbview_jobs_handler.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
21#include "gerber_file_image.h"
22#include "excellon_image.h"
23#include "gerber_to_png.h"
24#include "gerber_to_polyset.h"
25#include "gerber_diff.h"
29#include <cli/exit_codes.h>
30#include <reporter.h>
31#include <base_units.h>
32#include <json_common.h>
33#include <wx/file.h>
34#include <wx/filename.h>
35
36
38 JOB_DISPATCHER( aKiway )
39{
40 Register( "gerber_info", std::bind( &GERBVIEW_JOBS_HANDLER::JobGerberInfo, this, std::placeholders::_1 ),
41 []( JOB* job, wxWindow* aParent ) -> bool
42 {
43 return true;
44 } );
45
46 Register( "gerber_export_png", std::bind( &GERBVIEW_JOBS_HANDLER::JobGerberExportPng, this, std::placeholders::_1 ),
47 []( JOB* job, wxWindow* aParent ) -> bool
48 {
49 return true;
50 } );
51
52 Register( "gerber_diff", std::bind( &GERBVIEW_JOBS_HANDLER::JobGerberDiff, this, std::placeholders::_1 ),
53 []( JOB* job, wxWindow* aParent ) -> bool
54 {
55 return true;
56 } );
57}
58
59
60namespace
61{
62
63wxString GetFileTypeString( GERBER_FILE_IMAGE* aImage )
64{
65 if( dynamic_cast<EXCELLON_IMAGE*>( aImage ) )
66 return wxS( "Excellon Drill" );
67
68 return wxS( "Gerber" );
69}
70
71} // anonymous namespace
72
73
74bool GERBVIEW_JOBS_HANDLER::checkStrictMode( const wxArrayString& aMessages, bool aStrict )
75{
76 if( aMessages.IsEmpty() )
77 return true;
78
79 for( const wxString& msg : aMessages )
80 {
81 if( m_reporter )
82 m_reporter->Report( msg, aStrict ? RPT_SEVERITY_ERROR : RPT_SEVERITY_WARNING );
83 }
84
85 return !aStrict;
86}
87
88
90{
91 JOB_GERBER_INFO* infoJob = dynamic_cast<JOB_GERBER_INFO*>( aJob );
92
93 if( !infoJob )
95
96 wxString errorMsg;
97 wxArrayString messages;
98 auto image = LoadGerberOrExcellon( infoJob->m_inputFile, &errorMsg, &messages );
99
100 if( !image )
101 {
102 if( m_reporter )
103 m_reporter->Report( errorMsg, RPT_SEVERITY_ERROR );
104
106 }
107
108 if( !checkStrictMode( messages, infoJob->m_strict ) )
110
111 wxFileName fn( infoJob->m_inputFile );
112 BOX2I bbox = CalculateGerberBoundingBox( image.get() );
113
114 // Convert from gerbview IU to mm (gerbview uses 10nm per IU, so IU_PER_MM = 1e5)
115 double iuToMm = 1.0 / gerbIUScale.IU_PER_MM;
116
117 // Conversion factors from mm to output units
118 double lengthScale = 1.0;
119 double areaScale = 1.0;
120 wxString unitStr = wxS( "mm" );
121 wxString areaUnitStr = wxS( "mm²" );
122
123 switch( infoJob->m_units )
124 {
126 lengthScale = 1.0;
127 areaScale = 1.0;
128 unitStr = wxS( "mm" );
129 areaUnitStr = wxS( "mm²" );
130 break;
131
133 lengthScale = 1.0 / 25.4;
134 areaScale = 1.0 / ( 25.4 * 25.4 );
135 unitStr = wxS( "in" );
136 areaUnitStr = wxS( "in²" );
137 break;
138
140 lengthScale = 1000.0 / 25.4;
141 areaScale = 1000000.0 / ( 25.4 * 25.4 );
142 unitStr = wxS( "mils" );
143 areaUnitStr = wxS( "mils²" );
144 break;
145 }
146
147 double originX = static_cast<double>( bbox.GetOrigin().x ) * iuToMm * lengthScale;
148 double originY = static_cast<double>( bbox.GetOrigin().y ) * iuToMm * lengthScale;
149 double width = static_cast<double>( bbox.GetWidth() ) * iuToMm * lengthScale;
150 double height = static_cast<double>( bbox.GetHeight() ) * iuToMm * lengthScale;
151
152 int apertureCount = image->GetDcodesCount();
153
155 {
156 nlohmann::json j;
157
158 j["filename"] = fn.GetFullName().ToStdString();
159 j["type"] = GetFileTypeString( image.get() ).ToStdString();
160 j["item_count"] = image->GetItemsCount();
161 j["units"] = unitStr.ToStdString();
162
163 j["bounding_box"] = {
164 { "origin_x", originX }, { "origin_y", originY }, { "width", width }, { "height", height }
165 };
166
167 j["aperture_count"] = apertureCount;
168
169 if( infoJob->m_calculateArea )
170 {
171 double areaMm2 = image->CalculateCopperArea();
172 j["copper_area"] = areaMm2 * areaScale;
173 }
174
175 if( m_reporter )
176 m_reporter->Report( wxString::FromUTF8( j.dump( 2 ) ), RPT_SEVERITY_INFO );
177 }
178 else
179 {
180 wxString out;
181
182 out += wxString::Format( wxS( "File: %s\n" ), fn.GetFullName() );
183 out += wxString::Format( wxS( "Type: %s\n" ), GetFileTypeString( image.get() ) );
184 out += wxString::Format( wxS( "Item count: %d\n" ), image->GetItemsCount() );
185 out += wxS( "\n" );
186 out += wxS( "Bounding box:\n" );
187 out += wxString::Format( wxS( " Origin: (%.3f, %.3f) %s\n" ), originX, originY, unitStr );
188 out += wxString::Format( wxS( " Size: %.3f x %.3f %s\n" ), width, height, unitStr );
189
190 out += wxString::Format( wxS( "\nApertures defined: %d\n" ), apertureCount );
191
192 if( infoJob->m_calculateArea )
193 {
194 double areaMm2 = image->CalculateCopperArea();
195 out += wxString::Format( wxS( "Copper area: %.3f %s\n" ), areaMm2 * areaScale, areaUnitStr );
196 }
197
198 if( m_reporter )
199 m_reporter->Report( out, RPT_SEVERITY_INFO );
200 }
201
202 return CLI::EXIT_CODES::OK;
203}
204
205
207{
208 JOB_GERBER_EXPORT_PNG* pngJob = dynamic_cast<JOB_GERBER_EXPORT_PNG*>( aJob );
209
210 if( !pngJob )
212
213 wxString errorMsg;
214 wxArrayString messages;
215
216 if( !RenderGerberToPng( pngJob->m_inputFile, pngJob->GetConfiguredOutputPath(), *pngJob, &errorMsg, &messages ) )
217 {
218 if( m_reporter )
219 m_reporter->Report( errorMsg, RPT_SEVERITY_ERROR );
220
222 }
223
224 if( !checkStrictMode( messages, pngJob->m_strict ) )
226
227 if( m_reporter )
228 {
229 m_reporter->Report( wxString::Format( wxS( "Exported PNG to: %s" ), pngJob->GetConfiguredOutputPath() ),
231 }
232
233 return CLI::EXIT_CODES::OK;
234}
235
236
238{
239 JOB_GERBER_DIFF* diffJob = dynamic_cast<JOB_GERBER_DIFF*>( aJob );
240
241 if( !diffJob )
243
244 wxString errorMsg;
245 wxArrayString messagesA;
246 wxArrayString messagesB;
247
248 // Load both files
249 auto imageA = LoadGerberOrExcellon( diffJob->m_inputFileA, &errorMsg, &messagesA );
250
251 if( !imageA )
252 {
253 if( m_reporter )
254 m_reporter->Report( errorMsg, RPT_SEVERITY_ERROR );
255
257 }
258
259 if( !checkStrictMode( messagesA, diffJob->m_strict ) )
261
262 auto imageB = LoadGerberOrExcellon( diffJob->m_inputFileB, &errorMsg, &messagesB );
263
264 if( !imageB )
265 {
266 if( m_reporter )
267 m_reporter->Report( errorMsg, RPT_SEVERITY_ERROR );
268
270 }
271
272 if( !checkStrictMode( messagesB, diffJob->m_strict ) )
274
275 // Convert to poly sets
276 SHAPE_POLY_SET polyA = ConvertGerberToPolySet( imageA.get(), diffJob->m_tolerance );
277 SHAPE_POLY_SET polyB = ConvertGerberToPolySet( imageB.get(), diffJob->m_tolerance );
278
279 // Align bounding-box origins unless the caller explicitly opts out.
280 // Skip alignment when testing for absolute-placement correctness (wrong origin,
281 // unit conversion errors, etc.) that auto-alignment would silently hide.
282 if( !diffJob->m_noAlign )
283 {
284 VECTOR2I alignOffset = CalculateAlignment( polyA, polyB );
285 polyB.Move( alignOffset );
286 }
287
288 // Calculate differences
290
291 // 1e-9 mm² ≈ 1 µm², below which floating-point noise from boolean ops is expected
292 constexpr double DIFF_AREA_EPSILON_MM2 = 1e-9;
293
294 bool filesIdentical =
295 ( result.additionsArea < DIFF_AREA_EPSILON_MM2 && result.removalsArea < DIFF_AREA_EPSILON_MM2 );
296
297 if( diffJob->m_exitCodeOnly )
298 {
300 }
301
302 wxFileName fnA( diffJob->m_inputFileA );
303 wxFileName fnB( diffJob->m_inputFileB );
304
305 auto writeTextToOutput = [&]( const wxString& aText ) -> bool
306 {
307 wxString outputPath = diffJob->GetConfiguredOutputPath();
308
309 if( !outputPath.IsEmpty() )
310 {
311 wxFile file( outputPath, wxFile::write );
312
313 if( !file.IsOpened() )
314 {
315 if( m_reporter )
316 {
317 m_reporter->Report( wxString::Format( wxS( "Failed to open output file: %s" ), outputPath ),
319 }
320
321 return false;
322 }
323
324 file.Write( aText );
325
326 if( m_reporter )
327 {
328 m_reporter->Report( wxString::Format( wxS( "Output written to: %s" ), outputPath ),
330 }
331 }
332 else if( m_reporter )
333 {
334 m_reporter->Report( aText, RPT_SEVERITY_INFO );
335 }
336
337 return true;
338 };
339
340 switch( diffJob->m_outputFormat )
341 {
343 {
344 wxString text = FormatDiffResultText( result, fnA.GetFullName(), fnB.GetFullName() );
345
346 if( !writeTextToOutput( text ) )
348
349 break;
350 }
351
353 {
354 nlohmann::json j = FormatDiffResultJson( result, fnA.GetFullName(), fnB.GetFullName() );
355
356 if( !writeTextToOutput( wxString::FromUTF8( j.dump( 2 ) ) ) )
358
359 break;
360 }
361
363 {
364 DIFF_RENDER_OPTIONS options;
365 options.dpi = diffJob->m_dpi;
366 options.antialias = diffJob->m_antialias;
367
368 if( diffJob->m_transparentBackground )
369 options.colorBackground = KIGFX::COLOR4D( 0.0, 0.0, 0.0, 0.0 );
370
371 if( !RenderDiffToPng( result, diffJob->GetConfiguredOutputPath(), options ) )
372 {
373 if( m_reporter )
374 {
375 m_reporter->Report(
376 wxString::Format( wxS( "Failed to write PNG: %s" ), diffJob->GetConfiguredOutputPath() ),
378 }
379
381 }
382
383 if( m_reporter )
384 {
385 m_reporter->Report(
386 wxString::Format( wxS( "Diff PNG written to: %s" ), diffJob->GetConfiguredOutputPath() ),
388 }
389
390 break;
391 }
392 }
393
394 return CLI::EXIT_CODES::OK;
395}
constexpr EDA_IU_SCALE gerbIUScale
Definition base_units.h:111
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr const Vec & GetOrigin() const
Definition box2.h:210
Handle a drill image.
Hold the image data and parameters for one gerber file and layer parameters.
bool checkStrictMode(const wxArrayString &aMessages, bool aStrict)
void Register(const std::string &aJobTypeName, std::function< int(JOB *job)> aHandler, std::function< bool(JOB *job, wxWindow *aParent)> aConfigHandler)
JOB_DISPATCHER(KIWAY *aKiway)
REPORTER * m_reporter
Job to compare two Gerber files and highlight differences.
OUTPUT_FORMAT m_outputFormat
Job to convert Gerber/Excellon files to PNG images.
Job to extract information from Gerber/Excellon files.
OUTPUT_FORMAT m_outputFormat
An simple container class that lets us dispatch output jobs to kifaces.
Definition job.h:184
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
Definition job.h:233
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:315
Represent a set of closed polygons.
void Move(const VECTOR2I &aVector) override
wxString FormatDiffResultText(const GERBER_DIFF_RESULT &aResult, const wxString &aFile1, const wxString &aFile2, DIFF_UNITS aUnits)
Format a diff result as human-readable text.
GERBER_DIFF_RESULT CalculateGerberDiff(const SHAPE_POLY_SET &aReference, const SHAPE_POLY_SET &aComparison)
Calculate the geometric differences between two poly sets.
VECTOR2I CalculateAlignment(const SHAPE_POLY_SET &aReference, const SHAPE_POLY_SET &aComparison)
Calculate the alignment offset to make a comparison poly set align with a reference.
nlohmann::json FormatDiffResultJson(const GERBER_DIFF_RESULT &aResult, const wxString &aFile1, const wxString &aFile2, DIFF_UNITS aUnits, double aMaxDiffPercent)
Format a diff result as JSON.
bool RenderDiffToPng(const GERBER_DIFF_RESULT &aResult, const wxString &aOutputPath, const DIFF_RENDER_OPTIONS &aOptions)
Render a diff result to PNG with colored regions.
bool RenderGerberToPng(const wxString &aInputPath, const wxString &aOutputPath, const GERBER_RENDER_OPTIONS &aOptions, wxString *aErrorMsg, wxArrayString *aMessages)
Render a Gerber or Excellon file to PNG.
BOX2I CalculateGerberBoundingBox(GERBER_FILE_IMAGE *aImage)
Calculate bounding box for all draw items in a gerber image.
std::unique_ptr< GERBER_FILE_IMAGE > LoadGerberOrExcellon(const wxString &aPath, wxString *aErrorMsg, wxArrayString *aMessages)
Load a Gerber or Excellon file, auto-detecting by extension.
SHAPE_POLY_SET ConvertGerberToPolySet(GERBER_FILE_IMAGE *aImage, int aTolerance)
Convert a GERBER_FILE_IMAGE to a merged SHAPE_POLY_SET.
static const int OK
Definition exit_codes.h:30
static const int ERR_RC_VIOLATIONS
Rules check violation count was greater than 0.
Definition exit_codes.h:37
static const int ERR_INVALID_INPUT_FILE
Definition exit_codes.h:33
static const int ERR_UNKNOWN
Definition exit_codes.h:32
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
Options for diff PNG rendering.
KIGFX::COLOR4D colorBackground
White.
Result of a Gerber diff calculation containing the geometric differences and area statistics.
Definition gerber_diff.h:35
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687