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>
33#include "../../3d-viewer/3d_viewer/eda_3d_viewer_settings.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{
216 addCommonArgs( true, true, false, false );
217 addDefineArg();
218
219 m_argParser.add_description(
220 UTF8STDSTR( _( "Renders the PCB in 3D view to PNG or JPEG image" ) ) );
221
222 m_argParser.add_argument( ARG_WIDTH, ARG_WIDTH_SHORT )
223 .default_value( 1600 )
224 .scan<'i', int>()
225 .metavar( "WIDTH" )
226 .help( UTF8STDSTR( _( "Image width" ) ) );
227
229 .default_value( 900 )
230 .scan<'i', int>()
231 .metavar( "HEIGHT" )
232 .help( UTF8STDSTR( _( "Image height" ) ) );
233
234 m_argParser.add_argument( ARG_SIDE )
235 .default_value( std::string( "top" ) )
236 .add_choices( enumChoices<JOB_PCB_RENDER::SIDE>() )
237 .metavar( "SIDE" )
238 .help( UTF8STDSTR( wxString::Format( _( "Render from side. Options: %s" ),
239 enumString<JOB_PCB_RENDER::SIDE>() ) ) );
240
241 m_argParser.add_argument( ARG_BACKGROUND )
242 .default_value( std::string( "" ) )
243 .help( UTF8STDSTR( wxString::Format( _( "Image background. Options: %s. Default: "
244 "transparent for PNG, opaque for JPEG" ),
245 enumString<JOB_PCB_RENDER::BG_STYLE>() ) ) )
246 .metavar( "BG" );
247
248 m_argParser.add_argument( ARG_QUALITY )
249 .default_value( std::string( "basic" ) )
250 .add_choices( enumChoices<JOB_PCB_RENDER::QUALITY>() )
251 .metavar( "QUALITY" )
252 .help( UTF8STDSTR( wxString::Format( _( "Render quality. Options: %s" ),
253 enumString<JOB_PCB_RENDER::QUALITY>() ) ) );
254
255 m_argParser.add_argument( ARG_PRESET )
256 .default_value( std::string( wxString( FOLLOW_PLOT_SETTINGS ) ) )
257 .metavar( "PRESET" )
258 .help( UTF8STDSTR( wxString::Format( _( "Appearance preset. Options: %s, %s, or user-defined "
259 "preset name" ),
262
264 .default_value( true )
265 .implicit_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 renderJob->m_appearancePreset = m_argParser.get<std::string>( ARG_PRESET );
337
338 if( renderJob->m_appearancePreset == std::string(wxString(LEGACY_PRESET_FLAG).ToUTF8().data()) )
339 {
340 wxFprintf( stderr, _( "Invalid preset\n" ) );
342 }
343
344 renderJob->m_useBoardStackupColors = m_argParser.get<bool>( ARG_USE_BOARD_STACKUP_COLORS );
345
346 renderJob->m_width = m_argParser.get<int>( ARG_WIDTH );
347 renderJob->m_height = m_argParser.get<int>( ARG_HEIGHT );
348 renderJob->m_zoom = m_argParser.get<double>( ARG_ZOOM );
349 renderJob->m_perspective = m_argParser.get<bool>( ARG_PERSPECTIVE );
350 renderJob->m_floor = m_argParser.get<bool>( ARG_FLOOR );
351 renderJob->m_lightSideElevation = m_argParser.get<int>( ARG_LIGHT_SIDE_ELEVATION );
352
353 getToEnum( m_argParser.get<std::string>( ARG_QUALITY ), renderJob->m_quality );
354 getToEnum( m_argParser.get<std::string>( ARG_SIDE ), renderJob->m_side );
355
356 if( !getToEnum( m_argParser.get<std::string>( ARG_BACKGROUND ), renderJob->m_bgStyle ) )
357 {
358 wxFprintf( stderr, _( "Invalid background\n" ) );
360 }
361
362 if( !getToVector3( m_argParser.get<std::string>( ARG_ROTATE ), renderJob->m_rotation ) )
363 {
364 wxFprintf( stderr, _( "Invalid rotation format\n" ) );
366 }
367
368 if( !getToVector3( m_argParser.get<std::string>( ARG_PAN ), renderJob->m_pan ) )
369 {
370 wxFprintf( stderr, _( "Invalid pan format\n" ) );
372 }
373
374 if( !getToVector3( m_argParser.get<std::string>( ARG_PIVOT ), renderJob->m_pivot ) )
375 {
376 wxFprintf( stderr, _( "Invalid pivot format\n" ) );
378 }
379
380 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_TOP ),
381 renderJob->m_lightTopIntensity ) )
382 {
383 wxFprintf( stderr, _( "Invalid light top intensity format\n" ) );
385 }
386
387 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_BOTTOM ),
388 renderJob->m_lightBottomIntensity ) )
389 {
390 wxFprintf( stderr, _( "Invalid light bottom intensity format\n" ) );
392 }
393
394 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_SIDE ),
395 renderJob->m_lightSideIntensity ) )
396 {
397 wxFprintf( stderr, _( "Invalid light side intensity format\n" ) );
399 }
400
401 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_CAMERA ),
402 renderJob->m_lightCameraIntensity ) )
403 {
404 wxFprintf( stderr, _( "Invalid light camera intensity format\n" ) );
406 }
407
408 if( m_argOutput.Lower().EndsWith( wxS( ".png" ) ) )
409 {
410 renderJob->m_format = JOB_PCB_RENDER::FORMAT::PNG;
411 }
412 else if( m_argOutput.Lower().EndsWith( wxS( ".jpg" ) )
413 || m_argOutput.Lower().EndsWith( wxS( ".jpeg" ) ) )
414 {
415 renderJob->m_format = JOB_PCB_RENDER::FORMAT::JPEG;
416 }
417 else
418 {
419 wxFprintf( stderr, _( "Invalid image format\n" ) );
421 }
422
423 int exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, renderJob.get() );
424
425 return exitCode;
426}
const char * name
Definition: DXF_plotter.cpp:62
void addCommonArgs(bool aInput, bool aOutput, bool aInputCanBeDir, bool aOutputIsDir)
Set up the most common of args used across cli.
Definition: command.cpp:115
argparse::ArgumentParser m_argParser
Definition: command.h:100
void addDefineArg()
Set up the drawing sheet arg used by many of the export commands.
Definition: command.cpp:168
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:286
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Definition: kiway.cpp:678
@ FACE_PCB
pcbnew DSO
Definition: kiway.h:294
T y
Definition: vector3.h:64
T z
Definition: vector3.h:65
T x
Definition: vector3.h:63
#define UTF8STDSTR(s)
Definition: command.h:27
#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_BACKGROUND
#define ARG_LIGHT_SIDE
#define ARG_PERSPECTIVE
#define ARG_HEIGHT
#define ARG_PAN
static bool getColorOrIntensity(const std::string &aInput, VECTOR3D &aOutput)
#define ARG_LIGHT_CAMERA
#define ARG_PIVOT
#define ARG_WIDTH
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