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_PAN "--pan"
48#define ARG_PIVOT "--pivot"
49#define ARG_ROTATE "--rotate"
50#define ARG_ZOOM "--zoom"
51#define ARG_PERSPECTIVE "--perspective"
52#define ARG_FLOOR "--floor"
53
54#define ARG_LIGHT_TOP "--light-top"
55#define ARG_LIGHT_BOTTOM "--light-bottom"
56#define ARG_LIGHT_SIDE "--light-side"
57#define ARG_LIGHT_CAMERA "--light-camera"
58
59#define ARG_LIGHT_SIDE_ELEVATION "--light-side-elevation"
60
61
62template <typename T>
63static wxString enumString()
64{
65 wxString str;
66 auto names = magic_enum::enum_names<T>();
67
68 for( size_t i = 0; i < names.size(); i++ )
69 {
70 std::string name = { names[i].begin(), names[i].end() };
71
72 if( i > 0 )
73 str << ", ";
74
75 std::transform( name.begin(), name.end(), name.begin(),
76 []( unsigned char c )
77 {
78 return std::tolower( c );
79 } );
80
81 str << name;
82 }
83
84 return str;
85}
86
87
88template <typename T>
89static std::vector<std::string> enumChoices()
90{
91 std::vector<std::string> out;
92
93 for( auto& strView : magic_enum::enum_names<T>() )
94 {
95 std::string name = { strView.begin(), strView.end() };
96
97 std::transform( name.begin(), name.end(), name.begin(),
98 []( unsigned char c )
99 {
100 return std::tolower( c );
101 } );
102
103 out.emplace_back( name );
104 }
105
106 return out;
107}
108
109
110template <typename T>
111static std::optional<T> strToEnum( std::string& aInput )
112{
113 return magic_enum::enum_cast<T>( aInput, magic_enum::case_insensitive );
114}
115
116
117template <typename T>
118static bool getToEnum( const std::string& aInput, T& aOutput )
119{
120 // If not specified, leave at default
121 if( aInput.empty() )
122 return true;
123
124 if( auto opt = magic_enum::enum_cast<T>( aInput, magic_enum::case_insensitive ) )
125 {
126 aOutput = *opt;
127 return true;
128 }
129
130 return false;
131}
132
133
134static bool getToVector3( const std::string& aInput, VECTOR3D& aOutput )
135{
136 // If not specified, leave at default
137 if( aInput.empty() )
138 return true;
139
140 // Remove potential quotes
141 wxString wxStr = From_UTF8( aInput );
142
143 if( wxStr[0] == '\'' )
144 wxStr = wxStr.AfterFirst( '\'' );
145
146 if( wxStr[wxStr.length() - 1] == '\'' )
147 wxStr = wxStr.BeforeLast( '\'' );
148
149 wxArrayString arr = wxSplit( wxStr, ',', 0 );
150
151 if( arr.size() != 3 )
152 return false;
153
154 VECTOR3D vec;
155 bool success = true;
156 success &= arr[0].Trim().ToCDouble( &vec.x );
157 success &= arr[1].Trim().ToCDouble( &vec.y );
158 success &= arr[2].Trim().ToCDouble( &vec.z );
159
160 if( !success )
161 return false;
162
163 aOutput = vec;
164 return true;
165}
166
167
168static bool getColorOrIntensity( const std::string& aInput, VECTOR3D& aOutput )
169{
170 // If not specified, leave at default
171 if( aInput.empty() )
172 return true;
173
174 // Remove potential quotes
175 wxString wxStr = From_UTF8( aInput );
176
177 if( wxStr[0] == '\'' )
178 wxStr = wxStr.AfterFirst( '\'' );
179
180 if( wxStr[wxStr.length() - 1] == '\'' )
181 wxStr = wxStr.BeforeLast( '\'' );
182
183 wxArrayString arr = wxSplit( wxStr, ',', 0 );
184
185 if( arr.size() == 3 )
186 {
187 VECTOR3D vec;
188 bool success = true;
189 success &= arr[0].Trim().ToCDouble( &vec.x );
190 success &= arr[1].Trim().ToCDouble( &vec.y );
191 success &= arr[2].Trim().ToCDouble( &vec.z );
192
193 if( !success )
194 return false;
195
196 aOutput = vec;
197 return true;
198 }
199 else if( arr.size() == 1 )
200 {
201 double val;
202 if( arr[0].Trim().ToCDouble( &val ) )
203 {
204 aOutput = VECTOR3D( val, val, val );
205 return true;
206 }
207 }
208
209 return false;
210}
211
212
214{
215 addCommonArgs( true, true, false, false );
216 addDefineArg();
217
218 m_argParser.add_description(
219 UTF8STDSTR( _( "Renders the PCB in 3D view to PNG or JPEG image" ) ) );
220
221 m_argParser.add_argument( ARG_WIDTH, ARG_WIDTH_SHORT )
222 .default_value( 1600 )
223 .scan<'i', int>()
224 .metavar( "WIDTH" )
225 .help( UTF8STDSTR( _( "Image width" ) ) );
226
228 .default_value( 900 )
229 .scan<'i', int>()
230 .metavar( "HEIGHT" )
231 .help( UTF8STDSTR( _( "Image height" ) ) );
232
233 m_argParser.add_argument( ARG_SIDE )
234 .default_value( std::string( "top" ) )
235 .add_choices( enumChoices<JOB_PCB_RENDER::SIDE>() )
236 .metavar( "SIDE" )
237 .help( UTF8STDSTR( wxString::Format( _( "Render from side. Options: %s" ),
238 enumString<JOB_PCB_RENDER::SIDE>() ) ) );
239
240 m_argParser.add_argument( ARG_BACKGROUND )
241 .default_value( std::string( "" ) )
242 .help( UTF8STDSTR( wxString::Format( _( "Image background. Options: %s. Default: "
243 "transparent for PNG, opaque for JPEG" ),
244 enumString<JOB_PCB_RENDER::BG_STYLE>() ) ) )
245 .metavar( "BG" );
246
247 m_argParser.add_argument( ARG_QUALITY )
248 .default_value( std::string( "basic" ) )
249 .add_choices( enumChoices<JOB_PCB_RENDER::QUALITY>() )
250 .metavar( "QUALITY" )
251 .help( UTF8STDSTR( wxString::Format( _( "Render quality. Options: %s" ),
252 enumString<JOB_PCB_RENDER::QUALITY>() ) ) );
253
254 m_argParser.add_argument( ARG_PRESET )
255 .default_value( std::string( wxString( FOLLOW_PLOT_SETTINGS ) ) )
256 .metavar( "PRESET" )
257 .help( UTF8STDSTR( wxString::Format( _( "Color preset. Options: %s, %s, %s, ..." ),
259 LEGACY_PRESET_FLAG ) ) );
260
261 m_argParser.add_argument( ARG_FLOOR )
262 .flag()
263 .help( UTF8STDSTR( _( "Enables floor, shadows and post-processing, even if disabled in "
264 "quality preset" ) ) );
265
266 m_argParser.add_argument( ARG_PERSPECTIVE )
267 .flag()
268 .help( UTF8STDSTR( _( "Use perspective projection instead of orthogonal" ) ) );
269
270 m_argParser.add_argument( ARG_ZOOM )
271 .default_value( 1.0 )
272 .scan<'g', double>()
273 .metavar( "ZOOM" )
274 .help( UTF8STDSTR( _( "Camera zoom" ) ) );
275
276 m_argParser.add_argument( ARG_PAN )
277 .default_value( std::string( "" ) )
278 .metavar( "VECTOR" )
279 .help( UTF8STDSTR( _( "Pan camera, format 'X,Y,Z' e.g.: '3,0,0'" ) ) );
280
281 m_argParser.add_argument( ARG_PIVOT )
282 .default_value( std::string( "" ) )
283 .metavar( "PIVOT" )
284 .help( UTF8STDSTR( _( "Set pivot point relative to the board center in centimeters, format 'X,Y,Z' "
285 "e.g.: '-10,2,0'" ) ) );
286
287 m_argParser.add_argument( ARG_ROTATE )
288 .default_value( std::string( "" ) )
289 .metavar( "ANGLES" )
290 .help( UTF8STDSTR(
291 _( "Rotate board, format 'X,Y,Z' e.g.: '-45,0,45' for isometric view" ) ) );
292
293 m_argParser.add_argument( ARG_LIGHT_TOP )
294 .default_value( std::string( "" ) )
295 .metavar( "COLOR" )
296 .help( UTF8STDSTR( _( "Top light intensity, format 'R,G,B' or a single number, range: 0-1" ) ) );
297
298 m_argParser.add_argument( ARG_LIGHT_BOTTOM )
299 .default_value( std::string( "" ) )
300 .metavar( "COLOR" )
301 .help( UTF8STDSTR( _( "Bottom light intensity, format 'R,G,B' or a single number, range: 0-1" ) ) );
302
303 m_argParser.add_argument( ARG_LIGHT_SIDE )
304 .default_value( std::string( "" ) )
305 .metavar( "COLOR" )
306 .help( UTF8STDSTR( _( "Side lights intensity, format 'R,G,B' or a single number, range: 0-1" ) ) );
307
308 m_argParser.add_argument( ARG_LIGHT_CAMERA )
309 .default_value( std::string( "" ) )
310 .metavar( "COLOR" )
311 .help( UTF8STDSTR( _( "Camera light intensity, format 'R,G,B' or a single number, range: 0-1" ) ) );
312
314 .default_value( 60 )
315 .scan<'i', int>()
316 .metavar( "ANGLE" )
317 .help( UTF8STDSTR( _( "Side lights elevation angle in degrees, range: 0-90" ) ) );
318}
319
320
322{
323 std::unique_ptr<JOB_PCB_RENDER> renderJob( new JOB_PCB_RENDER() );
324
325 renderJob->SetConfiguredOutputPath( m_argOutput );
326 renderJob->m_filename = m_argInput;
327 renderJob->SetVarOverrides( m_argDefineVars );
328
329 renderJob->m_colorPreset = m_argParser.get<std::string>( ARG_PRESET );
330 renderJob->m_width = m_argParser.get<int>( ARG_WIDTH );
331 renderJob->m_height = m_argParser.get<int>( ARG_HEIGHT );
332 renderJob->m_zoom = m_argParser.get<double>( ARG_ZOOM );
333 renderJob->m_perspective = m_argParser.get<bool>( ARG_PERSPECTIVE );
334 renderJob->m_floor = m_argParser.get<bool>( ARG_FLOOR );
335 renderJob->m_lightSideElevation = m_argParser.get<int>( ARG_LIGHT_SIDE_ELEVATION );
336
337 getToEnum( m_argParser.get<std::string>( ARG_QUALITY ), renderJob->m_quality );
338 getToEnum( m_argParser.get<std::string>( ARG_SIDE ), renderJob->m_side );
339
340 if( !getToEnum( m_argParser.get<std::string>( ARG_BACKGROUND ), renderJob->m_bgStyle ) )
341 {
342 wxFprintf( stderr, _( "Invalid background\n" ) );
344 }
345
346 if( !getToVector3( m_argParser.get<std::string>( ARG_ROTATE ), renderJob->m_rotation ) )
347 {
348 wxFprintf( stderr, _( "Invalid rotation format\n" ) );
350 }
351
352 if( !getToVector3( m_argParser.get<std::string>( ARG_PAN ), renderJob->m_pan ) )
353 {
354 wxFprintf( stderr, _( "Invalid pan format\n" ) );
356 }
357
358 if( !getToVector3( m_argParser.get<std::string>( ARG_PIVOT ), renderJob->m_pivot ) )
359 {
360 wxFprintf( stderr, _( "Invalid pivot format\n" ) );
362 }
363
364 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_TOP ),
365 renderJob->m_lightTopIntensity ) )
366 {
367 wxFprintf( stderr, _( "Invalid light top intensity format\n" ) );
369 }
370
371 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_BOTTOM ),
372 renderJob->m_lightBottomIntensity ) )
373 {
374 wxFprintf( stderr, _( "Invalid light bottom intensity format\n" ) );
376 }
377
378 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_SIDE ),
379 renderJob->m_lightSideIntensity ) )
380 {
381 wxFprintf( stderr, _( "Invalid light side intensity format\n" ) );
383 }
384
385 if( !getColorOrIntensity( m_argParser.get<std::string>( ARG_LIGHT_CAMERA ),
386 renderJob->m_lightCameraIntensity ) )
387 {
388 wxFprintf( stderr, _( "Invalid light camera intensity format\n" ) );
390 }
391
392 if( m_argOutput.Lower().EndsWith( wxS( ".png" ) ) )
393 {
394 renderJob->m_format = JOB_PCB_RENDER::FORMAT::PNG;
395 }
396 else if( m_argOutput.Lower().EndsWith( wxS( ".jpg" ) )
397 || m_argOutput.Lower().EndsWith( wxS( ".jpeg" ) ) )
398 {
399 renderJob->m_format = JOB_PCB_RENDER::FORMAT::JPEG;
400 }
401 else
402 {
403 wxFprintf( stderr, _( "Invalid image format\n" ) );
405 }
406
407 int exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, renderJob.get() );
408
409 return exitCode;
410}
const char * name
Definition: DXF_plotter.cpp:59
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:169
void addCommonArgs(bool aInput, bool aOutput, bool aInputIsDir, bool aOutputIsDir)
Set up the most common of args used across cli.
Definition: command.cpp:115
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:285
@ FACE_PCB
pcbnew DSO
Definition: kiway.h:293
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr)
Definition: kiway.cpp:711
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_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