KiCad PCB EDA Suite
Loading...
Searching...
No Matches
command_pcb_render.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 (C) 2022 Mark Roszko <[email protected]>
5 * Copyright (C) 2024 Alex Shvartzkop <[email protected]>
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "command_pcb_render.h"
23#include <cli/exit_codes.h>
24#include "jobs/job_pcb_render.h"
25#include <kiface_base.h>
26#include <layer_ids.h>
27#include <string_utils.h>
28#include <wx/crt.h>
29#include <magic_enum.hpp>
30
31#include <macros.h>
32#include <wx/tokenzr.h>
34#include <math/vector3.h>
35
36#define ARG_BACKGROUND "--background"
37#define ARG_QUALITY "--quality"
38
39#define ARG_WIDTH "--width"
40#define ARG_WIDTH_SHORT "-w"
41
42#define ARG_HEIGHT "--height"
43#define ARG_HEIGHT_SHORT "-h"
44
45#define ARG_SIDE "--side"
46#define ARG_PRESET "--preset"
47#define ARG_USE_BOARD_STACKUP_COLORS "--use-board-stackup-colors"
48#define ARG_PAN "--pan"
49#define ARG_PIVOT "--pivot"
50#define ARG_ROTATE "--rotate"
51#define ARG_ZOOM "--zoom"
52#define ARG_PERSPECTIVE "--perspective"
53#define ARG_FLOOR "--floor"
54
55#define ARG_LIGHT_TOP "--light-top"
56#define ARG_LIGHT_BOTTOM "--light-bottom"
57#define ARG_LIGHT_SIDE "--light-side"
58#define ARG_LIGHT_CAMERA "--light-camera"
59
60#define ARG_LIGHT_SIDE_ELEVATION "--light-side-elevation"
61
62
63template <typename T>
64static wxString enumString()
65{
66 wxString str;
67 auto names = magic_enum::enum_names<T>();
68
69 for( size_t i = 0; i < names.size(); i++ )
70 {
71 std::string name = { names[i].begin(), names[i].end() };
72
73 if( i > 0 )
74 str << ", ";
75
76 std::transform( name.begin(), name.end(), name.begin(),
77 []( unsigned char c )
78 {
79 return std::tolower( c );
80 } );
81
82 str << name;
83 }
84
85 return str;
86}
87
88
89template <typename T>
90static std::vector<std::string> enumChoices()
91{
92 std::vector<std::string> out;
93
94 for( auto& strView : magic_enum::enum_names<T>() )
95 {
96 std::string name = { strView.begin(), strView.end() };
97
98 std::transform( name.begin(), name.end(), name.begin(),
99 []( unsigned char c )
100 {
101 return std::tolower( c );
102 } );
103
104 out.emplace_back( name );
105 }
106
107 return out;
108}
109
110
111template <typename T>
112static std::optional<T> strToEnum( std::string& aInput )
113{
114 return magic_enum::enum_cast<T>( aInput, magic_enum::case_insensitive );
115}
116
117
118template <typename T>
119static bool getToEnum( const std::string& aInput, T& aOutput )
120{
121 // If not specified, leave at default
122 if( aInput.empty() )
123 return true;
124
125 if( auto opt = magic_enum::enum_cast<T>( aInput, magic_enum::case_insensitive ) )
126 {
127 aOutput = *opt;
128 return true;
129 }
130
131 return false;
132}
133
134
135static bool getToVector3( const std::string& aInput, VECTOR3D& aOutput )
136{
137 // If not specified, leave at default
138 if( aInput.empty() )
139 return true;
140
141 // Remove potential quotes
142 wxString wxStr = From_UTF8( aInput );
143
144 if( wxStr[0] == '\'' )
145 wxStr = wxStr.AfterFirst( '\'' );
146
147 if( wxStr[wxStr.length() - 1] == '\'' )
148 wxStr = wxStr.BeforeLast( '\'' );
149
150 wxArrayString arr = wxSplit( wxStr, ',', 0 );
151
152 if( arr.size() != 3 )
153 return false;
154
155 VECTOR3D vec;
156 bool success = true;
157 success &= arr[0].Trim().ToCDouble( &vec.x );
158 success &= arr[1].Trim().ToCDouble( &vec.y );
159 success &= arr[2].Trim().ToCDouble( &vec.z );
160
161 if( !success )
162 return false;
163
164 aOutput = vec;
165 return true;
166}
167
168
169static bool getColorOrIntensity( const std::string& aInput, VECTOR3D& aOutput )
170{
171 // If not specified, leave at default
172 if( aInput.empty() )
173 return true;
174
175 // Remove potential quotes
176 wxString wxStr = From_UTF8( aInput );
177
178 if( wxStr[0] == '\'' )
179 wxStr = wxStr.AfterFirst( '\'' );
180
181 if( wxStr[wxStr.length() - 1] == '\'' )
182 wxStr = wxStr.BeforeLast( '\'' );
183
184 wxArrayString arr = wxSplit( wxStr, ',', 0 );
185
186 if( arr.size() == 3 )
187 {
188 VECTOR3D vec;
189 bool success = true;
190 success &= arr[0].Trim().ToCDouble( &vec.x );
191 success &= arr[1].Trim().ToCDouble( &vec.y );
192 success &= arr[2].Trim().ToCDouble( &vec.z );
193
194 if( !success )
195 return false;
196
197 aOutput = vec;
198 return true;
199 }
200 else if( arr.size() == 1 )
201 {
202 double val;
203 if( arr[0].Trim().ToCDouble( &val ) )
204 {
205 aOutput = VECTOR3D( val, val, val );
206 return true;
207 }
208 }
209
210 return false;
211}
212
213
215{
217 addDefineArg();
219
220 m_argParser.add_description(
221 UTF8STDSTR( _( "Renders the PCB in 3D view to PNG or JPEG image" ) ) );
222
223 m_argParser.add_argument( ARG_WIDTH, ARG_WIDTH_SHORT )
224 .default_value( 1600 )
225 .scan<'i', int>()
226 .metavar( "WIDTH" )
227 .help( UTF8STDSTR( _( "Image width" ) ) );
228
230 .default_value( 900 )
231 .scan<'i', int>()
232 .metavar( "HEIGHT" )
233 .help( UTF8STDSTR( _( "Image height" ) ) );
234
235 m_argParser.add_argument( ARG_SIDE )
236 .default_value( std::string( "top" ) )
237 .add_choices( enumChoices<JOB_PCB_RENDER::SIDE>() )
238 .metavar( "SIDE" )
239 .help( UTF8STDSTR( wxString::Format( _( "Render from side. Options: %s" ),
241
242 m_argParser.add_argument( ARG_BACKGROUND )
243 .default_value( std::string( "" ) )
244 .help( UTF8STDSTR( wxString::Format( _( "Image background. Options: %s. Default: "
245 "transparent for PNG, opaque for JPEG" ),
247 .metavar( "BG" );
248
249 m_argParser.add_argument( ARG_QUALITY )
250 .default_value( std::string( "basic" ) )
252 .metavar( "QUALITY" )
253 .help( UTF8STDSTR( wxString::Format( _( "Render quality. Options: %s" ),
255
256 m_argParser.add_argument( ARG_PRESET )
257 .default_value( std::string( wxString( FOLLOW_PLOT_SETTINGS ) ) )
258 .metavar( "PRESET" )
259 .help( UTF8STDSTR( wxString::Format( _( "Appearance preset. Options: %s, %s, or user-defined "
260 "preset name" ),
263
265 .default_value( true )
266 .help( UTF8STDSTR( _( "Colors defined in board stackup override those in preset" ) ) );
267
268 m_argParser.add_argument( ARG_FLOOR )
269 .flag()
270 .help( UTF8STDSTR( _( "Enables floor, shadows and post-processing, even if disabled in "
271 "quality setting" ) ) );
272
273 m_argParser.add_argument( ARG_PERSPECTIVE )
274 .flag()
275 .help( UTF8STDSTR( _( "Use perspective projection instead of orthogonal" ) ) );
276
277 m_argParser.add_argument( ARG_ZOOM )
278 .default_value( 1.0 )
279 .scan<'g', double>()
280 .metavar( "ZOOM" )
281 .help( UTF8STDSTR( _( "Camera zoom" ) ) );
282
283 m_argParser.add_argument( ARG_PAN )
284 .default_value( std::string( "" ) )
285 .metavar( "VECTOR" )
286 .help( UTF8STDSTR( _( "Pan camera, format 'X,Y,Z' e.g.: '3,0,0'" ) ) );
287
288 m_argParser.add_argument( ARG_PIVOT )
289 .default_value( std::string( "" ) )
290 .metavar( "PIVOT" )
291 .help( UTF8STDSTR( _( "Set pivot point relative to the board center in centimeters, format 'X,Y,Z' "
292 "e.g.: '-10,2,0'" ) ) );
293
294 m_argParser.add_argument( ARG_ROTATE )
295 .default_value( std::string( "" ) )
296 .metavar( "ANGLES" )
297 .help( UTF8STDSTR(
298 _( "Rotate board, format 'X,Y,Z' e.g.: '-45,0,45' for isometric view" ) ) );
299
300 m_argParser.add_argument( ARG_LIGHT_TOP )
301 .default_value( std::string( "" ) )
302 .metavar( "COLOR" )
303 .help( UTF8STDSTR( _( "Top light intensity, format 'R,G,B' or a single number, range: 0-1" ) ) );
304
305 m_argParser.add_argument( ARG_LIGHT_BOTTOM )
306 .default_value( std::string( "" ) )
307 .metavar( "COLOR" )
308 .help( UTF8STDSTR( _( "Bottom light intensity, format 'R,G,B' or a single number, range: 0-1" ) ) );
309
310 m_argParser.add_argument( ARG_LIGHT_SIDE )
311 .default_value( std::string( "" ) )
312 .metavar( "COLOR" )
313 .help( UTF8STDSTR( _( "Side lights intensity, format 'R,G,B' or a single number, range: 0-1" ) ) );
314
315 m_argParser.add_argument( ARG_LIGHT_CAMERA )
316 .default_value( std::string( "" ) )
317 .metavar( "COLOR" )
318 .help( UTF8STDSTR( _( "Camera light intensity, format 'R,G,B' or a single number, range: 0-1" ) ) );
319
321 .default_value( 60 )
322 .scan<'i', int>()
323 .metavar( "ANGLE" )
324 .help( UTF8STDSTR( _( "Side lights elevation angle in degrees, range: 0-90" ) ) );
325}
326
327
329{
330 std::unique_ptr<JOB_PCB_RENDER> renderJob( new JOB_PCB_RENDER() );
331
332 renderJob->SetConfiguredOutputPath( m_argOutput );
333 renderJob->m_filename = m_argInput;
334 renderJob->SetVarOverrides( m_argDefineVars );
335
336 if( !m_argVariantNames.empty() )
337 renderJob->m_variant = m_argVariantNames.front();
338
339 renderJob->m_appearancePreset = m_argParser.get<std::string>( ARG_PRESET );
340
341 if( renderJob->m_appearancePreset == std::string(wxString(LEGACY_PRESET_FLAG).ToUTF8().data()) )
342 {
343 wxFprintf( stderr, _( "Invalid preset\n" ) );
345 }
346
347 renderJob->m_useBoardStackupColors = m_argParser.get<bool>( ARG_USE_BOARD_STACKUP_COLORS );
348
349 renderJob->m_width = m_argParser.get<int>( ARG_WIDTH );
350 renderJob->m_height = m_argParser.get<int>( ARG_HEIGHT );
351 renderJob->m_zoom = m_argParser.get<double>( ARG_ZOOM );
352 renderJob->m_perspective = m_argParser.get<bool>( ARG_PERSPECTIVE );
353 renderJob->m_floor = m_argParser.get<bool>( ARG_FLOOR );
354 renderJob->m_lightSideElevation = m_argParser.get<int>( ARG_LIGHT_SIDE_ELEVATION );
355
356 getToEnum( m_argParser.get<std::string>( ARG_QUALITY ), renderJob->m_quality );
357 getToEnum( m_argParser.get<std::string>( ARG_SIDE ), renderJob->m_side );
358
359 if( !getToEnum( m_argParser.get<std::string>( ARG_BACKGROUND ), renderJob->m_bgStyle ) )
360 {
361 wxFprintf( stderr, _( "Invalid background\n" ) );
363 }
364
365 if( !getToVector3( m_argParser.get<std::string>( ARG_ROTATE ), renderJob->m_rotation ) )
366 {
367 wxFprintf( stderr, _( "Invalid rotation format\n" ) );
369 }
370
371 if( !getToVector3( m_argParser.get<std::string>( ARG_PAN ), renderJob->m_pan ) )
372 {
373 wxFprintf( stderr, _( "Invalid pan format\n" ) );
375 }
376
377 if( !getToVector3( m_argParser.get<std::string>( ARG_PIVOT ), renderJob->m_pivot ) )
378 {
379 wxFprintf( stderr, _( "Invalid pivot format\n" ) );
381 }
382
383 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_TOP ),
384 renderJob->m_lightTopIntensity ) )
385 {
386 wxFprintf( stderr, _( "Invalid light top intensity format\n" ) );
388 }
389
390 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_BOTTOM ),
391 renderJob->m_lightBottomIntensity ) )
392 {
393 wxFprintf( stderr, _( "Invalid light bottom intensity format\n" ) );
395 }
396
397 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_SIDE ),
398 renderJob->m_lightSideIntensity ) )
399 {
400 wxFprintf( stderr, _( "Invalid light side intensity format\n" ) );
402 }
403
404 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_CAMERA ),
405 renderJob->m_lightCameraIntensity ) )
406 {
407 wxFprintf( stderr, _( "Invalid light camera intensity format\n" ) );
409 }
410
411 if( m_argOutput.Lower().EndsWith( wxS( ".png" ) ) )
412 {
413 renderJob->m_format = JOB_PCB_RENDER::FORMAT::PNG;
414 }
415 else if( m_argOutput.Lower().EndsWith( wxS( ".jpg" ) )
416 || m_argOutput.Lower().EndsWith( wxS( ".jpeg" ) ) )
417 {
418 renderJob->m_format = JOB_PCB_RENDER::FORMAT::JPEG;
419 }
420 else
421 {
422 wxFprintf( stderr, _( "Invalid image format\n" ) );
424 }
425
426 int exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, renderJob.get() );
427
428 return exitCode;
429}
const char * name
std::map< wxString, wxString > m_argDefineVars
Value of the drawing sheet arg if configured.
Definition command.h:158
void addVariantsArg()
Set up the list of variants to output arguement.
Definition command.cpp:227
argparse::ArgumentParser m_argParser
Definition command.h:113
COMMAND(const std::string &aName)
Define a new COMMAND instance.
Definition command.cpp:30
void addDefineArg()
Set up the drawing sheet arg used by many of the export commands.
Definition command.cpp:212
void addCommonArgs(bool aInput, bool aOutput, IO_TYPE aInputType, IO_TYPE aOutputType)
Set up the most common of args used across cli.
Definition command.cpp:132
wxString m_argOutput
Value of the output arg if configured.
Definition command.h:148
wxString m_argInput
Value of the common input arg if configured.
Definition command.h:143
std::vector< wxString > m_argVariantNames
The list of variant names to output.
Definition command.h:170
int doPerform(KIWAY &aKiway) override
The internal handler that should be overloaded to implement command specific processing and work.
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:315
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Definition kiway.cpp:750
@ FACE_PCB
pcbnew DSO
Definition kiway.h:323
#define UTF8STDSTR(s)
Definition command.h:27
#define ARG_BACKGROUND
#define ARG_HEIGHT
#define ARG_WIDTH
#define ARG_SIDE
#define ARG_FLOOR
#define ARG_USE_BOARD_STACKUP_COLORS
#define ARG_WIDTH_SHORT
static bool getToVector3(const std::string &aInput, VECTOR3D &aOutput)
#define ARG_HEIGHT_SHORT
#define ARG_PRESET
#define ARG_ROTATE
#define ARG_LIGHT_SIDE
#define ARG_PERSPECTIVE
#define ARG_PAN
static bool getColorOrIntensity(const std::string &aInput, VECTOR3D &aOutput)
#define ARG_LIGHT_CAMERA
#define ARG_PIVOT
static bool getToEnum(const std::string &aInput, T &aOutput)
static std::vector< std::string > enumChoices()
#define ARG_LIGHT_BOTTOM
#define ARG_LIGHT_SIDE_ELEVATION
static wxString enumString()
#define ARG_ZOOM
#define ARG_QUALITY
static std::optional< T > strToEnum(std::string &aInput)
#define ARG_LIGHT_TOP
#define _(s)
#define FOLLOW_PLOT_SETTINGS
#define LEGACY_PRESET_FLAG
#define FOLLOW_PCB
This file contains miscellaneous commonly used macros and functions.
static const int ERR_ARGS
Definition exit_codes.h:31
wxString From_UTF8(const char *cstring)
VECTOR3< double > VECTOR3D
Definition vector3.h:230