KiCad PCB EDA Suite
Loading...
Searching...
No Matches
eda_3d_viewer_frame.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) 2015-2016 Mario Luzeiro <[email protected]>
5 * Copyright (C) 2023 CERN
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <wx/filename.h>
23#include <wx/msgdlg.h>
24#include <wx/string.h>
25#include <wx/wupdlock.h>
26#include <wx/clipbrd.h>
27#include <wx/filedlg.h>
28#include <wx/dialog.h>
29#include "eda_3d_viewer_frame.h"
34#include <3d_viewer_id.h>
38#include <board.h>
39#include <advanced_config.h>
40#include <bitmaps.h>
42#include <core/arraydim.h>
43#include <dpi_scaling_common.h>
44#include <pgm_base.h>
45#include <project.h>
49#include <tool/action_manager.h>
50#include <tool/common_control.h>
51#include <tool/tool_manager.h>
53#include <tool/action_toolbar.h>
54#include <widgets/wx_infobar.h>
57#include <kiplatform/ui.h>
58#include <project_pcb.h>
59#include <toolbars_3d.h>
60
61#if defined(__linux__) || defined(__FreeBSD__)
64#else
66#endif
67
75const wxChar* EDA_3D_VIEWER_FRAME::m_logTrace = wxT( "KI_TRACE_EDA_3D_VIEWER" );
76
77
78BEGIN_EVENT_TABLE( EDA_3D_VIEWER_FRAME, KIWAY_PLAYER )
79
81 EVT_SET_FOCUS( EDA_3D_VIEWER_FRAME::OnSetFocus )
82
84 EDA_3D_VIEWER_FRAME::Process_Special_Functions )
85
86 EVT_MENU( wxID_CLOSE, EDA_3D_VIEWER_FRAME::Exit3DFrame )
88
89 EVT_CLOSE( EDA_3D_VIEWER_FRAME::OnCloseWindow )
90END_EVENT_TABLE()
91
92
94 const wxString& aTitle, long style ) :
95 KIWAY_PLAYER( aKiway, aParent, FRAME_PCB_DISPLAY3D, aTitle, wxDefaultPosition,
96 wxDefaultSize, style, QUALIFIED_VIEWER3D_FRAMENAME( aParent ), unityScale ),
97 m_canvasHolder( nullptr ),
98 m_canvas( nullptr ),
99 m_currentCamera( m_trackBallCamera ),
100 m_trackBallCamera( 2 * RANGE_SCALE_3D )
101{
102 wxLogTrace( m_logTrace, wxT( "EDA_3D_VIEWER_FRAME::EDA_3D_VIEWER_FRAME %s" ), aTitle );
103
104 m_disable_ray_tracing = false;
105 m_aboutTitle = _HKI( "KiCad 3D Viewer" );
106
107 // Give it an icon
108 wxIcon icon;
109 icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_3d ) );
110 SetIcon( icon );
111
112 // Create the status line
113 std::vector<int> status_dims{ -1,
114 -1,
115 -1,
116 GetTextExtent( wxS( "dx -88.88" ) ).x + 10,
117 GetTextExtent( wxS( "dy -88.88" ) ).x + 10,
118 GetTextExtent( wxS( "zoom -88.88" ) ).x + 10 };
119
120 wxStatusBar* status_bar = CreateStatusBar( status_dims.size() );
121 SetStatusWidths( status_dims.size(), status_dims.data() );
122
125
126 if( cfg )
127 aaMode = static_cast<ANTIALIASING_MODE>( cfg->m_Render.opengl_AA_mode );
128
129 m_canvasHolder = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER );
130
131 m_canvas = new EDA_3D_CANVAS( m_canvasHolder, OGL_ATT_LIST::GetAttributesList( aaMode, true ),
132 m_boardAdapter, m_currentCamera,
134
135 wxBoxSizer* canvasSizer = new wxBoxSizer( wxVERTICAL );
136 canvasSizer->Add( m_canvas, 1, wxEXPAND, 0 );
137 m_canvasHolder->SetSizer( canvasSizer );
138
139 m_appearancePanel = new APPEARANCE_CONTROLS_3D( this, GetCanvas() );
140
141 LoadSettings( GetAppSettings<EDA_3D_VIEWER_SETTINGS>( "3d_viewer" ) );
142 loadCommonSettings();
143
144 m_appearancePanel->SetUserViewports( Prj().GetProjectFile().m_Viewports3D );
145
146 // Create the manager
147 m_toolManager = new TOOL_MANAGER;
148 m_toolManager->SetEnvironment( GetBoard(), nullptr, nullptr,
149 GetAppSettings<EDA_3D_VIEWER_SETTINGS>( "3d_viewer" ), this );
150
151 m_actions = new EDA_3D_ACTIONS();
152 m_toolDispatcher = new TOOL_DISPATCHER( m_toolManager );
153 m_canvas->SetEventDispatcher( m_toolDispatcher );
154
155 // Register tools
156 m_toolManager->RegisterTool( new COMMON_CONTROL );
157 m_toolManager->RegisterTool( new EDA_3D_CONTROLLER );
158 m_toolManager->InitTools();
159
160 setupUIConditions();
161
162 if( EDA_3D_CONTROLLER* ctrlTool = GetToolManager()->GetTool<EDA_3D_CONTROLLER>() )
163 ctrlTool->SetRotationIncrement( cfg ? cfg->m_Camera.rotation_increment : 10.0 );
164
165 // Run the viewer control tool, it is supposed to be always active
166 m_toolManager->InvokeTool( "3DViewer.Control" );
167
168 ReCreateMenuBar();
169
170 m_toolbarSettings = Pgm().GetSettingsManager().GetToolbarSettings<EDA_3D_VIEWER_TOOLBAR_SETTINGS>( "3d_viewer-toolbars" );
171 configureToolbars();
172 RecreateToolbars();
173
174 m_infoBar = new WX_INFOBAR( m_canvasHolder, wxID_ANY, true );
175
176 m_auimgr.SetManagedWindow( this );
177
178 m_auimgr.AddPane( m_tbTopMain, EDA_PANE().HToolbar().Name( wxS( "TopMainToolbar" ) )
179 .Top().Layer( 6 ) );
180 m_auimgr.AddPane( m_appearancePanel, EDA_PANE().Name( "LayersManager" )
181 .Right().Layer( 3 )
182 .Caption( _( "Appearance" ) ).PaneBorder( false )
183 .MinSize( FromDIP( 180 ), -1 ).BestSize( FromDIP( 190 ), -1 ) );
184 m_auimgr.AddPane( m_canvasHolder, EDA_PANE().Canvas().Name( wxS( "DrawFrame" ) )
185 .Center() );
186
187 wxAuiPaneInfo& layersManager = m_auimgr.GetPane( "LayersManager" );
188
189 if( cfg && cfg->m_AuiPanels.right_panel_width > 0 )
190 SetAuiPaneSize( m_auimgr, layersManager, cfg->m_AuiPanels.right_panel_width, -1 );
191
192 if( cfg )
193 layersManager.Show( cfg->m_AuiPanels.show_layer_manager );
194
195 m_auimgr.Update();
196
197 m_canvas->SetInfoBar( m_infoBar );
198 m_canvas->SetStatusBar( status_bar );
199
200 try
201 {
202#if defined(__linux__) || defined(__FreeBSD__)
203 m_spaceMouse = std::make_unique<SPNAV_VIEWER_PLUGIN>( m_canvas );
204#else
205 m_spaceMouse = std::make_unique<NL_3D_VIEWER_PLUGIN>( m_canvas );
206#endif
207 }
208 catch( const std::exception& e )
209 {
210 wxLogTrace( wxT( "KI_TRACE_NAVLIB" ), wxS( "%s" ), e.what() );
211 }
212 catch( ... )
213 {
214 wxLogTrace( wxT( "KI_TRACE_NAVLIB" ),
215 wxT( "Unknown exception during SpaceMouse initialization" ) );
216 }
217
218 // Fixes bug in Windows (XP and possibly others) where the canvas requires the focus
219 // in order to receive mouse events. Otherwise, the user has to click somewhere on
220 // the canvas before it will respond to mouse wheel events.
221 m_canvas->SetFocus();
222}
223
224
226{
227 // Shutdown all running tools
228 if( m_toolManager )
229 m_toolManager->ShutdownAllTools();
230
231 Prj().GetProjectFile().m_Viewports3D = m_appearancePanel->GetUserViewports();
232
233 m_canvas->SetEventDispatcher( nullptr );
234
235 m_auimgr.UnInit();
236
237 delete m_canvas;
238 m_canvas = nullptr;
239}
240
241
246
247
249{
251
252 ACTION_MANAGER* mgr = m_toolManager->GetActionManager();
254
255// Helper to define check conditions
256#define GridSizeCheck( x ) ACTION_CONDITIONS().Check( cond.GridSize( x ) )
257
258 auto raytracing =
259 [this]( const SELECTION& aSel )
260 {
261 return m_boardAdapter.m_Cfg->m_Render.engine != RENDER_ENGINE::OPENGL;
262 };
263 auto showMissingModels =
264 [this]( const SELECTION& aSel )
265 {
266 return m_boardAdapter.m_Cfg->m_Render.show_missing_models;
267 };
268 auto showTH =
269 [this]( const SELECTION& aSel )
270 {
271 return m_boardAdapter.m_Cfg->m_Render.show_footprints_normal;
272 };
273 auto showSMD =
274 [this]( const SELECTION& aSel )
275 {
276 return m_boardAdapter.m_Cfg->m_Render.show_footprints_insert;
277 };
278 auto showVirtual =
279 [this]( const SELECTION& aSel )
280 {
281 return m_boardAdapter.m_Cfg->m_Render.show_footprints_virtual;
282 };
283 auto show_NotInPosfile =
284 [this]( const SELECTION& aSel )
285 {
286 return m_boardAdapter.m_Cfg->m_Render.show_footprints_not_in_posfile;
287 };
288 auto show_DNP =
289 [this]( const SELECTION& aSel )
290 {
291 return m_boardAdapter.m_Cfg->m_Render.show_footprints_dnp;
292 };
293 auto showBBoxes =
294 [this]( const SELECTION& aSel )
295 {
296 return m_boardAdapter.m_Cfg->m_Render.show_model_bbox;
297 };
298 auto showNavig =
299 [this]( const SELECTION& aSel )
300 {
301 return m_boardAdapter.m_Cfg->m_Render.show_navigator;
302 };
303 auto ortho =
304 [this]( const SELECTION& )
305 {
306 return m_currentCamera.GetProjection() == PROJECTION_TYPE::ORTHO;
307 };
308
309 auto appearances =
310 [this]( const SELECTION& aSel )
311 {
312 return m_boardAdapter.m_Cfg->m_AuiPanels.show_layer_manager;
313 };
314
316 mgr->SetConditions( EDA_3D_ACTIONS::toggleShowMissingModels, ACTION_CONDITIONS().Check( showMissingModels ) );
317
319 mgr->SetConditions( EDA_3D_ACTIONS::showSMD, ACTION_CONDITIONS().Check( showSMD ) );
320 mgr->SetConditions( EDA_3D_ACTIONS::showVirtual, ACTION_CONDITIONS().Check( showVirtual ) );
322 ACTION_CONDITIONS().Check( show_NotInPosfile ) );
323 mgr->SetConditions( EDA_3D_ACTIONS::showDNP, ACTION_CONDITIONS().Check( show_DNP ) );
324
325 mgr->SetConditions( EDA_3D_ACTIONS::showBBoxes, ACTION_CONDITIONS().Check( showBBoxes ) );
327
334
336 ACTION_CONDITIONS().Check( appearances ) );
337
338#undef GridSizeCheck
339}
340
341
342bool EDA_3D_VIEWER_FRAME::TryBefore( wxEvent& aEvent )
343{
344 static bool s_presetSwitcherShown = false;
345 static bool s_viewportSwitcherShown = false;
346
347 // wxWidgets generates no key events for the tab key when the ctrl key is held down. One
348 // way around this is to look at all events and inspect the keyboard state of the tab key.
349 // However, this runs into issues on some linux VMs where querying the keyboard state is
350 // very slow. Fortunately we only use ctrl-tab on Mac, so we implement this lovely hack:
351#ifdef __WXMAC__
352 if( wxGetKeyState( WXK_TAB ) )
353#else
354 if( ( aEvent.GetEventType() == wxEVT_CHAR || aEvent.GetEventType() == wxEVT_CHAR_HOOK )
355 && static_cast<wxKeyEvent&>( aEvent ).GetKeyCode() == WXK_TAB )
356#endif
357 {
358 if( !s_presetSwitcherShown && wxGetKeyState( PRESET_SWITCH_KEY ) )
359 {
360 if( m_appearancePanel && this->IsActive() )
361 {
362 wxArrayString mru = m_appearancePanel->GetLayerPresetsMRU();
363
364 if( mru.size() > 0 )
365 {
366 for( wxString& str : mru )
367 {
368 if( str == FOLLOW_PCB )
369 str = _( "Follow PCB Editor" );
370 else if( str == FOLLOW_PLOT_SETTINGS )
371 str = _( "Follow PCB Plot Settings" );
372 }
373
374 EDA_VIEW_SWITCHER switcher( this, mru, PRESET_SWITCH_KEY );
375
376 s_presetSwitcherShown = true;
377 switcher.ShowModal();
378 s_presetSwitcherShown = false;
379
380 int idx = switcher.GetSelection();
381
382 if( idx >= 0 && idx < (int) mru.size() )
383 {
384 wxString internalName = m_appearancePanel->GetLayerPresetsMRU()[idx];
385 m_appearancePanel->ApplyLayerPreset( internalName );
386 }
387
388 return true;
389 }
390 }
391 }
392 else if( !s_viewportSwitcherShown && wxGetKeyState( VIEWPORT_SWITCH_KEY ) )
393 {
394 if( this->IsActive() )
395 {
396 const wxArrayString& viewportMRU = m_appearancePanel->GetViewportsMRU();
397
398 if( viewportMRU.size() > 0 )
399 {
400 EDA_VIEW_SWITCHER switcher( this, viewportMRU, VIEWPORT_SWITCH_KEY );
401
402 s_viewportSwitcherShown = true;
403 switcher.ShowModal();
404 s_viewportSwitcherShown = false;
405
406 int idx = switcher.GetSelection();
407
408 if( idx >= 0 && idx < (int) viewportMRU.size() )
409 m_appearancePanel->ApplyViewport( viewportMRU[idx] );
410
411 return true;
412 }
413 }
414 }
415 }
416
417 return wxFrame::TryBefore( aEvent );
418}
419
420
421void EDA_3D_VIEWER_FRAME::handleIconizeEvent( wxIconizeEvent& aEvent )
422{
424
425 if( m_spaceMouse && aEvent.IsIconized() )
426 m_spaceMouse->SetFocus( false );
427}
428
429
431{
432 // This will schedule a request to load later
433 // ReloadRequest also updates the board pointer so always call it first
434 if( m_canvas )
435 m_canvas->ReloadRequest( GetBoard(), PROJECT_PCB::Get3DCacheManager( &Prj() ) );
436
438 m_appearancePanel->UpdateLayerCtls();
439}
440
441
442void EDA_3D_VIEWER_FRAME::NewDisplay( bool aForceImmediateRedraw )
443{
444 if( m_canvas )
445 m_canvas->ReloadRequest( GetBoard(), PROJECT_PCB::Get3DCacheManager( &Prj() ) );
446
447 // After the ReloadRequest call, the refresh often takes a bit of time,
448 // and it is made here only on request.
449 if( m_canvas && aForceImmediateRedraw )
450 m_canvas->Refresh();
451}
452
453
455{
456 // Only update in OpenGL for an interactive interaction
457 if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
458 m_canvas->Request_refresh( true );
459}
460
461
463{
464 if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
465 m_canvas->Request_refresh();
466 else
467 NewDisplay( true );
468}
469
470
471void EDA_3D_VIEWER_FRAME::Exit3DFrame( wxCommandEvent &event )
472{
473 wxLogTrace( m_logTrace, wxT( "EDA_3D_VIEWER_FRAME::Exit3DFrame" ) );
474
475 Close( true );
476}
477
478
479void EDA_3D_VIEWER_FRAME::OnCloseWindow( wxCloseEvent &event )
480{
481 wxLogTrace( m_logTrace, wxT( "EDA_3D_VIEWER_FRAME::OnCloseWindow" ) );
482
483 // Do not show the layer manager during closing to avoid flicker on some platforms (Windows)
484 // that generate useless redraw of items in the Layer Manager
485 if( m_auimgr.GetPane( wxS( "LayersManager" ) ).IsShown() )
486 m_auimgr.GetPane( wxS( "LayersManager" ) ).Show( false );
487
488 if( m_canvas )
489 m_canvas->Close();
490
491 Destroy();
492 event.Skip( true );
493}
494
495
497{
498 if( m_canvas == nullptr )
499 return;
500
501 switch( event.GetId() )
502 {
504 {
505 m_boardAdapter.SetLayerColors( m_boardAdapter.GetDefaultColors() );
506
508 cfg->ResetToDefaults();
509
511
512 // Tell canvas that we (may have) changed the render engine
514 NewDisplay( true );
515 return;
516 }
517
518 default:
519 wxFAIL_MSG( wxT( "Invalid event in EDA_3D_VIEWER_FRAME::Process_Special_Functions()" ) );
520 return;
521 }
522}
523
524
525void EDA_3D_VIEWER_FRAME::onDisableRayTracing( wxCommandEvent& aEvent )
526{
527 wxLogTrace( m_logTrace, wxT( "EDA_3D_VIEWER_FRAME::%s disabling ray tracing." ), __WXFUNCTION__ );
528
530 m_boardAdapter.m_Cfg->m_Render.engine = RENDER_ENGINE::OPENGL;
531}
532
533
534void EDA_3D_VIEWER_FRAME::OnActivate( wxActivateEvent &aEvent )
535{
536 wxLogTrace( m_logTrace, wxT( "EDA_3D_VIEWER_FRAME::OnActivate" ) );
537
538 if( aEvent.GetActive() && m_canvas )
539 {
540 // Reload data if 3D frame shows a board,
541 // because it can be changed since last frame activation
542 if( m_canvas->IsReloadRequestPending() )
543 m_canvas->Request_refresh();
544
545 // Activates again the focus of the canvas so it will catch mouse and key events
546 m_canvas->SetFocus();
547 }
548
549 if( m_spaceMouse )
550 m_spaceMouse->SetFocus( aEvent.GetActive() );
551
552 aEvent.Skip(); // required under wxMAC
553}
554
555
556void EDA_3D_VIEWER_FRAME::OnSetFocus( wxFocusEvent& aEvent )
557{
558 // Activates again the focus of the canvas so it will catch mouse and key events
559 if( m_canvas )
560 m_canvas->SetFocus();
561
562 aEvent.Skip();
563}
564
565
567{
569
570 // Dynamic_cast here will fail on Mac when called from CvPCB.
571 EDA_3D_VIEWER_SETTINGS* cfg = static_cast<EDA_3D_VIEWER_SETTINGS*>( aCfg );
572
573 wxLogTrace( m_logTrace, wxT( "EDA_3D_VIEWER_FRAME::LoadSettings" ) );
574
575 if( cfg )
576 {
577 applySettings( cfg );
578
579 m_boardAdapter.SetBoard( GetBoard() );
580
581 // When opening the 3D viewer, we use the OpenGL mode, never the ray tracing engine
582 // because the ray tracing is very time consuming, and can be seen as not working
583 // (freeze window) with large boards.
584 m_boardAdapter.m_Cfg->m_Render.engine = RENDER_ENGINE::OPENGL;
585
587 {
588 wxString legacyColorsPresetName = _( "legacy colors" );
589
590 cfg->m_UseStackupColors = false;
591
592 if( !cfg->FindPreset( legacyColorsPresetName ) )
593 {
594 cfg->m_LayerPresets.emplace_back( legacyColorsPresetName,
595 GetAdapter().GetDefaultVisibleLayers(),
596 GetAdapter().GetDefaultColors() );
597 }
598
600 }
601
602 m_boardAdapter.InitSettings( nullptr, nullptr );
603
605 m_appearancePanel->CommonSettingsChanged();
606 }
607}
608
609
611{
613
615 {
616 cfg->m_AuiPanels.right_panel_width = m_appearancePanel->GetSize().x;
617
618 cfg->m_Camera.animation_enabled = m_canvas->GetAnimationEnabled();
619 cfg->m_Camera.moving_speed_multiplier = m_canvas->GetMovingSpeedMultiplier();
620 cfg->m_Camera.projection_mode = m_canvas->GetProjectionMode();
621 }
622}
623
624
626{
627 wxLogTrace( m_logTrace, wxT( "EDA_3D_VIEWER_FRAME::CommonSettingsChanged" ) );
628
629 // Regen menu bars, etc
631
633
635
636 if( cfg )
637 applySettings( cfg );
638
639 if( EDA_3D_CONTROLLER* ctrlTool = GetToolManager()->GetTool<EDA_3D_CONTROLLER>() )
640 ctrlTool->SetRotationIncrement( cfg ? cfg->m_Camera.rotation_increment : 10.0 );
641
642 m_appearancePanel->CommonSettingsChanged();
643
644 NewDisplay( true );
645}
646
647
649{
651
652 SetTitle( _( "3D Viewer" ) );
654
656 {
657 wxAuiPaneInfo& lm_pane_info = m_auimgr.GetPane( m_appearancePanel );
658 lm_pane_info.Caption( _( "Appearance" ) );
659 }
660
661 SetStatusText( wxEmptyString, ACTIVITY );
662 SetStatusText( wxEmptyString, RENDER_TIME );
663 SetStatusText( wxEmptyString, HOVERED_ITEM );
664}
665
666
668{
669 wxAuiPaneInfo& layersManager = m_auimgr.GetPane( "LayersManager" );
670
672 {
673 // show auxiliary Vertical layers and visibility manager toolbar
674 cfg->m_AuiPanels.show_layer_manager = !cfg->m_AuiPanels.show_layer_manager;
675
676 layersManager.Show( cfg->m_AuiPanels.show_layer_manager );
677
678 if( cfg->m_AuiPanels.show_layer_manager )
679 {
680 SetAuiPaneSize( m_auimgr, layersManager, cfg->m_AuiPanels.right_panel_width, -1 );
681 }
682 else
683 {
684 cfg->m_AuiPanels.right_panel_width = m_appearancePanel->GetSize().x;
685 m_auimgr.Update();
686 }
687 }
688}
689
690
692{
694 m_appearancePanel->OnDarkModeToggle();
695}
696
697
699{
700 wxString fullFileName;
701
703 {
704 if( !getExportFileName( aFormat, fullFileName ) )
705 return;
706 }
707
708 wxImage screenshotImage = captureCurrentViewScreenshot();
709
710 if( screenshotImage.IsOk() )
711 {
712 saveOrCopyImage( screenshotImage, aFormat, fullFileName );
713 }
714}
715
716
718{
719 // Ensure we have the latest 3D view (remember 3D view is buffered)
720 // Also ensure any highlighted item is not highlighted when creating screen shot
722 bool original_highlight = cfg.highlight_on_rollover;
723 cfg.highlight_on_rollover = false;
724
725 if( m_canvas )
726 {
727 // Ensure any in-progress background loading finishes before capture.
728 m_canvas->JoinBgWorker();
729 m_canvas->DoRePaint(); // init first buffer
730 m_canvas->JoinBgWorker();
731 m_canvas->DoRePaint(); // init second buffer
732 }
733
734 wxImage screenshotImage;
735
736 if( m_canvas )
737 {
738 // Build image from the 3D buffer
739 wxWindowUpdateLocker noUpdates( this );
740 m_canvas->GetScreenshot( screenshotImage );
741 }
742
743 // Restore highlight setting
744 cfg.highlight_on_rollover = original_highlight;
745
746 return screenshotImage;
747}
748
749
751{
752 wxString fullFileName;
753
755 {
756 if( !getExportFileName( aFormat, fullFileName ) )
757 return;
758 }
759
760 wxImage screenshotImage = captureScreenshot( aSize );
761
762 if( screenshotImage.IsOk() )
763 {
764 saveOrCopyImage( screenshotImage, aFormat, fullFileName );
765 }
766}
767
768
770{
771 // Create combined wildcard for both formats
772 const wxString wildcard = FILEEXT::JpegFileWildcard() + "|" + FILEEXT::PngFileWildcard();
773
776
777 // Set default extension based on current format
778 const wxString defaultExt = ( aFormat == EDA_3D_VIEWER_EXPORT_FORMAT::JPEG ) ?
780 m_defaultSaveScreenshotFileName.SetExt( defaultExt );
781
782 wxFileDialog dlg( this, _( "3D Image File Name" ),
784 m_defaultSaveScreenshotFileName.GetFullName(), wildcard,
785 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
786
788
789 // Set initial filter index based on current format
790 dlg.SetFilterIndex( ( aFormat == EDA_3D_VIEWER_EXPORT_FORMAT::JPEG ) ? 0 : 1 );
791
792 if( dlg.ShowModal() == wxID_CANCEL )
793 return false;
794
795 m_defaultSaveScreenshotFileName = dlg.GetPath();
796
797 // Determine format based on file extension first
798 wxString fileExt = m_defaultSaveScreenshotFileName.GetExt().Lower();
799 EDA_3D_VIEWER_EXPORT_FORMAT detectedFormat;
800 bool formatDetected = false;
801
802 if( fileExt == wxT("jpg") || fileExt == wxT("jpeg") )
803 {
804 detectedFormat = EDA_3D_VIEWER_EXPORT_FORMAT::JPEG;
805 formatDetected = true;
806 }
807 else if( fileExt == wxT("png") )
808 {
809 detectedFormat = EDA_3D_VIEWER_EXPORT_FORMAT::PNG;
810 formatDetected = true;
811 }
812
813 // If format can't be determined from extension, use dropdown selection
814 if( !formatDetected )
815 {
816 int filterIndex = dlg.GetFilterIndex();
817 detectedFormat = ( filterIndex == 0 ) ? EDA_3D_VIEWER_EXPORT_FORMAT::JPEG :
819
820 // Append appropriate extension
821 const wxString ext = ( detectedFormat == EDA_3D_VIEWER_EXPORT_FORMAT::JPEG ) ?
824 }
825
826 // Update the format parameter
827 aFormat = detectedFormat;
828
829 // Check directory permissions using the updated filename
830 wxFileName fn = m_defaultSaveScreenshotFileName;
831
832 if( !fn.IsDirWritable() )
833 {
834 wxString msg;
835 msg.Printf( _( "Insufficient permissions to save file '%s'." ),
836 m_defaultSaveScreenshotFileName.GetFullPath() );
837 wxMessageBox( msg, _( "Error" ), wxOK | wxICON_ERROR, this );
838 return false;
839 }
840
841 fullFileName = m_defaultSaveScreenshotFileName.GetFullPath();
842 return true;
843}
844
845
846wxImage EDA_3D_VIEWER_FRAME::captureScreenshot( const wxSize& aSize )
847{
849 camera.SetCurWindowSize( aSize );
850
852 EDA_3D_VIEWER_SETTINGS* backupCfg = m_boardAdapter.m_Cfg;
853
854 auto configRestorer = std::unique_ptr<void, std::function<void(void*)>>(
855 reinterpret_cast<void*>(1),
856 [&](void*) { m_boardAdapter.m_Cfg = backupCfg; }
857 );
858
859 if( cfg )
860 m_boardAdapter.m_Cfg = cfg;
861
862 if( cfg && cfg->m_Render.engine == RENDER_ENGINE::RAYTRACING )
863 return captureRaytracingScreenshot( m_boardAdapter, camera, aSize );
864 else
865 return captureOpenGLScreenshot( m_boardAdapter, camera, aSize );
866}
867
868
870{
872
873 if( cfg )
874 aAdapter.m_Cfg = cfg;
875}
876
877
878wxImage EDA_3D_VIEWER_FRAME::captureRaytracingScreenshot( BOARD_ADAPTER& aAdapter, TRACK_BALL& aCamera, const wxSize& aSize )
879{
880 BOARD_ADAPTER tempadapter;
881 tempadapter.SetBoard( GetBoard() );
882 tempadapter.m_Cfg = aAdapter.m_Cfg;
883 tempadapter.InitSettings( nullptr, nullptr );
884 tempadapter.CreateLayers( nullptr );
885 tempadapter.Set3dCacheManager( aAdapter.Get3dCacheManager() );
886
887 RENDER_3D_RAYTRACE_RAM raytrace( tempadapter, aCamera );
888 raytrace.SetCurWindowSize( aSize );
889
890 while( raytrace.Redraw( false ) );
891
892 uint8_t* rgbaBuffer = raytrace.GetBuffer();
893 wxSize realSize = raytrace.GetRealBufferSize();
894
895 if( !rgbaBuffer )
896 return wxImage();
897
898 return convertRGBAToImage( rgbaBuffer, realSize );
899}
900
901
902wxImage EDA_3D_VIEWER_FRAME::convertRGBAToImage( uint8_t* aRGBABuffer, const wxSize& aRealSize )
903{
904 const unsigned int wxh = aRealSize.x * aRealSize.y;
905
906 unsigned char* rgbBuffer = (unsigned char*) malloc( wxh * 3 );
907 unsigned char* alphaBuffer = (unsigned char*) malloc( wxh );
908
909 unsigned char* rgbaPtr = aRGBABuffer;
910 unsigned char* rgbPtr = rgbBuffer;
911 unsigned char* alphaPtr = alphaBuffer;
912
913 for( int y = 0; y < aRealSize.y; y++ )
914 {
915 for( int x = 0; x < aRealSize.x; x++ )
916 {
917 rgbPtr[0] = rgbaPtr[0];
918 rgbPtr[1] = rgbaPtr[1];
919 rgbPtr[2] = rgbaPtr[2];
920 alphaPtr[0] = rgbaPtr[3];
921
922 rgbaPtr += 4;
923 rgbPtr += 3;
924 alphaPtr += 1;
925 }
926 }
927
928 wxImage screenshotImage;
929 screenshotImage.Create( aRealSize );
930 screenshotImage.SetData( rgbBuffer );
931 screenshotImage.SetAlpha( alphaBuffer );
932 return screenshotImage.Mirror( false );
933}
934
935
936wxImage EDA_3D_VIEWER_FRAME::captureOpenGLScreenshot( BOARD_ADAPTER& aAdapter, TRACK_BALL& aCamera, const wxSize& aSize )
937{
938 EDA_3D_VIEWER_SETTINGS* cfg = aAdapter.m_Cfg;
939 ANTIALIASING_MODE aaMode = cfg ? static_cast<ANTIALIASING_MODE>( cfg->m_Render.opengl_AA_mode )
941
942 wxFrame temp( this, wxID_ANY, wxEmptyString, wxDefaultPosition, aSize, wxFRAME_NO_TASKBAR );
943 temp.Hide();
944 BOARD_ADAPTER tempadapter;
945 tempadapter.SetBoard( GetBoard() );
946 tempadapter.m_Cfg = aAdapter.m_Cfg;
947 tempadapter.InitSettings( nullptr, nullptr );
948 tempadapter.CreateLayers( nullptr );
949 tempadapter.Set3dCacheManager( aAdapter.Get3dCacheManager() );
950
951 auto canvas = std::make_unique<EDA_3D_CANVAS>( &temp,
952 OGL_ATT_LIST::GetAttributesList( aaMode, true ),
953 tempadapter, aCamera,
954 aAdapter.Get3dCacheManager() );
955
956 canvas->SetSize( aSize );
957 configureCanvas( canvas, cfg );
958 wxWindowUpdateLocker noUpdates( this );
959
960 // Temporarily disable highlight during screenshot
962 bool original_highlight = renderCfg.highlight_on_rollover;
963 bool original_navigator = renderCfg.show_navigator;
964 renderCfg.show_navigator = false;
965 renderCfg.highlight_on_rollover = false;
966
967 std::vector<unsigned char> buffer(aSize.x * aSize.y * 4); // RGBA format
968 canvas->RenderToFrameBuffer( buffer.data(), aSize.x, aSize.y );
969 wxImage result = convertRGBAToImage( buffer.data(), aSize );
970
971 // Restore highlight setting
972 renderCfg.highlight_on_rollover = original_highlight;
973 renderCfg.show_navigator = original_navigator;
974
975 return result;
976}
977
978
979void EDA_3D_VIEWER_FRAME::configureCanvas( std::unique_ptr<EDA_3D_CANVAS>& aCanvas, EDA_3D_VIEWER_SETTINGS* aCfg )
980{
981 if( aCfg )
982 {
983 aCanvas->SetAnimationEnabled( aCfg->m_Camera.animation_enabled );
984 aCanvas->SetMovingSpeedMultiplier( aCfg->m_Camera.moving_speed_multiplier );
985 aCanvas->SetProjectionMode( aCfg->m_Camera.projection_mode );
986 }
987
988 aCanvas->SetVcSettings( EDA_DRAW_PANEL_GAL::GetVcSettings() );
989}
990
991
992void EDA_3D_VIEWER_FRAME::saveOrCopyImage( const wxImage& aScreenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString& aFullFileName )
993{
995 {
996 copyImageToClipboard( aScreenshotImage );
997 }
998 else
999 {
1000 saveImageToFile( aScreenshotImage, aFormat, aFullFileName );
1001 }
1002}
1003
1004
1005void EDA_3D_VIEWER_FRAME::copyImageToClipboard( const wxImage& aScreenshotImage )
1006{
1007 wxBitmap bitmap( aScreenshotImage );
1008 wxLogNull doNotLog;
1009
1010 if( wxTheClipboard->Open() )
1011 {
1012 wxBitmapDataObject* dobjBmp = new wxBitmapDataObject( bitmap );
1013
1014 if( !wxTheClipboard->SetData( dobjBmp ) )
1015 wxMessageBox( _( "Failed to copy image to clipboard" ) );
1016
1017 wxTheClipboard->Flush();
1018 wxTheClipboard->Close();
1019 }
1020}
1021
1022
1023void EDA_3D_VIEWER_FRAME::saveImageToFile( const wxImage& aScreenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString& aFullFileName )
1024{
1025 bool fmt_is_jpeg = ( aFormat == EDA_3D_VIEWER_EXPORT_FORMAT::JPEG );
1026
1027 if( !aScreenshotImage.SaveFile( aFullFileName, fmt_is_jpeg ? wxBITMAP_TYPE_JPEG : wxBITMAP_TYPE_PNG ) )
1028 {
1029 wxMessageBox( _( "Can't save file" ) );
1030 }
1031}
1032
1033
1035{
1036 wxLogTrace( m_logTrace, wxT( "EDA_3D_VIEWER_FRAME::RenderEngineChanged()" ) );
1037
1038 if( m_canvas )
1039 m_canvas->RenderEngineChanged();
1040}
1041
1042
1044{
1045 wxCHECK_RET( m_canvas, wxT( "Cannot load settings to null canvas" ) );
1046
1047 COMMON_SETTINGS* settings = Pgm().GetCommonSettings();
1048
1049 // TODO(JE) use all control options
1050 m_boardAdapter.m_MousewheelPanning = settings->m_Input.scroll_modifier_zoom != 0;
1051}
1052
1053
1055{
1056 m_boardAdapter.m_Cfg = cfg;
1057
1058 m_canvas->SetAnimationEnabled( cfg->m_Camera.animation_enabled );
1059 m_canvas->SetMovingSpeedMultiplier( cfg->m_Camera.moving_speed_multiplier );
1060 m_canvas->SetProjectionMode( cfg->m_Camera.projection_mode );
1061
1062 m_canvas->SetVcSettings( EDA_DRAW_PANEL_GAL::GetVcSettings() );
1063}
@ ID_DISABLE_RAY_TRACING
@ ID_START_COMMAND_3D
@ ID_MENU_COMMAND_END
@ ID_MENU3D_RESET_DEFAULTS
constexpr EDA_IU_SCALE unityScale
Definition base_units.h:124
wxBitmap KiBitmap(BITMAPS aBitmap, int aHeightTag)
Construct a wxBitmap from an image identifier Returns the image from the active theme if the image ha...
Definition bitmap.cpp:100
#define RANGE_SCALE_3D
This defines the range that all coord will have to be rendered.
Manage TOOL_ACTION objects.
void SetConditions(const TOOL_ACTION &aAction, const ACTION_CONDITIONS &aConditions)
Set the conditions the UI elements for activating a specific tool action should use for determining t...
APP_SETTINGS_BASE is a settings class that should be derived for each standalone KiCad application.
Helper class to handle information needed to display 3D board.
void CreateLayers(std::shared_ptr< REPORTER > aStatusReporter, std::stop_token aStop=std::stop_token())
Creates the layers for the board, should be called after InitSettings.
void InitSettings(std::shared_ptr< REPORTER > aStatusReporter, std::shared_ptr< REPORTER > aWarningReporter)
Function to be called by the render when it need to reload the settings for the board.
void SetBoard(BOARD *aBoard) noexcept
Set current board to be rendered.
EDA_3D_VIEWER_SETTINGS * m_Cfg
void Set3dCacheManager(S3D_CACHE *aCacheMgr) noexcept
Update the cache manager pointer.
S3D_CACHE * Get3dCacheManager() const noexcept
bool SetCurWindowSize(const wxSize &aSize)
Update the windows size of the camera.
Definition camera.cpp:567
Handle actions that are shared between different applications.
int ShowModal() override
EDA_3D_ACTIONS.
static TOOL_ACTION showNavigator
static TOOL_ACTION showLayersManager
static TOOL_ACTION showNotInPosFile
static TOOL_ACTION showTHT
static TOOL_ACTION noGrid
static TOOL_ACTION show2_5mmGrid
static TOOL_ACTION show1mmGrid
static TOOL_ACTION showDNP
static TOOL_ACTION toggleOrtho
static TOOL_ACTION toggleShowMissingModels
static TOOL_ACTION show10mmGrid
static TOOL_ACTION toggleRaytacing
static TOOL_ACTION show5mmGrid
static TOOL_ACTION showSMD
static TOOL_ACTION showVirtual
static TOOL_ACTION showBBoxes
Implement a canvas based on a wxGLCanvas.
Handle view actions for various 3D canvases.
Create and handle a window for the 3d viewer connected to a Kiway and a pcbboard.
void saveOrCopyImage(const wxImage &screenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString &fullFileName)
Save image to file or copy to clipboard based on format.
void OnSetFocus(wxFocusEvent &event)
void onDisableRayTracing(wxCommandEvent &aEvent)
void CommonSettingsChanged(int aFlags) override
Notification that common settings are updated.
void applySettings(EDA_3D_VIEWER_SETTINGS *aSettings)
PCB_BASE_FRAME * Parent() const
void SaveSettings(APP_SETTINGS_BASE *aCfg) override
Save common frame parameters to a configuration data file.
void NewDisplay(bool aForceImmediateRedraw=false)
Reload and refresh (rebuild) the 3D scene.
void configureCanvas(std::unique_ptr< EDA_3D_CANVAS > &canvas, EDA_3D_VIEWER_SETTINGS *cfg)
Configure canvas settings for screenshot capture.
wxImage captureOpenGLScreenshot(BOARD_ADAPTER &adapter, TRACK_BALL &camera, const wxSize &aSize)
Capture screenshot using OpenGL renderer.
bool getExportFileName(EDA_3D_VIEWER_EXPORT_FORMAT &aFormat, wxString &fullFileName)
Get export filename through file dialog.
void setupUIConditions() override
Setup the UI conditions for the various actions and their controls in this frame.
void OnActivate(wxActivateEvent &event)
bool TryBefore(wxEvent &aEvent) override
void Exit3DFrame(wxCommandEvent &event)
Called when user press the File->Exit.
std::unique_ptr< NL_3D_VIEWER_PLUGIN > m_spaceMouse
void RenderEngineChanged()
RenderEngineChanged - Update toolbar icon and call canvas RenderEngineChanged.
void Process_Special_Functions(wxCommandEvent &event)
wxImage captureCurrentViewScreenshot()
Capture screenshot of the current view using the configured renderer.
void OnCloseWindow(wxCloseEvent &event)
void ShowChangedLanguage() override
wxImage captureScreenshot(const wxSize &aSize)
Capture screenshot using appropriate rendering method.
void setupRenderingConfig(BOARD_ADAPTER &adapter)
Setup rendering configuration for screenshot capture.
void ReloadRequest()
Request reloading the 3D view.
wxFileName m_defaultSaveScreenshotFileName
wxImage convertRGBAToImage(uint8_t *rgbaBuffer, const wxSize &realSize)
Convert RGBA buffer to wxImage format.
APP_SETTINGS_BASE * config() const override
Return the settings object used in SaveSettings(), and is overloaded in KICAD_MANAGER_FRAME.
BOARD_ADAPTER & GetAdapter()
void LoadSettings(APP_SETTINGS_BASE *aCfg) override
Load common frame parameters from a configuration file.
void saveImageToFile(const wxImage &screenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString &fullFileName)
Save image to file.
void copyImageToClipboard(const wxImage &screenshotImage)
Copy image to system clipboard.
void loadCommonSettings()
Load configuration from common settings.
void ExportImage(EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxSize &aSize)
Export 3D viewer image to file or clipboard.
wxImage captureRaytracingScreenshot(BOARD_ADAPTER &adapter, TRACK_BALL &camera, const wxSize &aSize)
Capture screenshot using raytracing renderer.
void TakeScreenshot(EDA_3D_VIEWER_EXPORT_FORMAT aFormat)
Create a Screenshot of the current 3D view.
void handleIconizeEvent(wxIconizeEvent &aEvent) override
Handle a window iconize event.
LAYER_PRESET_3D * FindPreset(const wxString &aName)
std::vector< LAYER_PRESET_3D > m_LayerPresets
Toolbar configuration for the 3D viewer.
Definition toolbars_3d.h:29
virtual void handleIconizeEvent(wxIconizeEvent &aEvent)
Handle a window iconize event.
APPEARANCE_CONTROLS_3D * m_appearancePanel
void CommonSettingsChanged(int aFlags) override
Notification event that some of the common (suite-wide) settings have changed.
void ShowChangedLanguage() override
Redraw the menus and what not in current language.
virtual void setupUIConditions()
Setup the UI conditions for the various actions and their controls in this frame.
wxAuiManager m_auimgr
virtual void RecreateToolbars()
virtual void LoadSettings(APP_SETTINGS_BASE *aCfg)
Load common frame parameters from a configuration file.
virtual void SaveSettings(APP_SETTINGS_BASE *aCfg)
Save common frame parameters to a configuration data file.
static KIGFX::VC_SETTINGS GetVcSettings()
Gets a populated View Controls settings object dervived from our program settings.
Specialization of the wxAuiPaneInfo class for KiCad panels.
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
A wxFrame capable of the OpenProjectFiles function, meaning it can load a portion of a KiCad project.
bool Destroy() override
Our version of Destroy() which is virtual from wxWidgets.
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:340
static const wxGLAttributes GetAttributesList(ANTIALIASING_MODE aAntiAliasingMode, bool aAlpha=false)
Get a list of attributes to pass to wxGLCanvas.
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:546
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
std::vector< VIEWPORT3D > m_Viewports3D
List of stored viewports (pos + zoom)
static S3D_CACHE * Get3DCacheManager(PROJECT *aProject, bool updateProjDir=false)
Return a pointer to an instance of the 3D cache manager.
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition project.cpp:177
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:201
bool Redraw(bool aIsMoving) override
Redraw the view.
void SetCurWindowSize(const wxSize &aSize) override
Before each render, the canvas will tell the render what is the size of its windows,...
T * GetToolbarSettings(const wxString &aFilename)
Return a handle to the given toolbar settings.
TOOL_MANAGER * m_toolManager
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
Master controller class:
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
A modified version of the wxInfoBar class that allows us to:
Definition wx_infobar.h:76
#define _(s)
#define GridSizeCheck(x)
EVT_TOOL_RANGE(ID_START_COMMAND_3D, ID_MENU_COMMAND_END, EDA_3D_VIEWER_FRAME::Process_Special_Functions) EDA_3D_VIEWER_FRAME
Declaration of the eda_3d_viewer class.
EDA_3D_VIEWER_EXPORT_FORMAT
@ HOVERED_ITEM
@ RENDER_TIME
#define FOLLOW_PLOT_SETTINGS
#define LEGACY_PRESET_FLAG
#define FOLLOW_PCB
#define QUALIFIED_VIEWER3D_FRAMENAME(parent)
#define VIEWPORT_SWITCH_KEY
#define PRESET_SWITCH_KEY
@ FRAME_PCB_DISPLAY3D
Definition frame_type.h:43
static const std::string JpegFileExtension
static const std::string PngFileExtension
static wxString PngFileWildcard()
static wxString JpegFileWildcard()
static const wxChar * m_logTrace
Trace mask used to enable or disable the trace output of this class.
PROJECT & Prj()
Definition kicad.cpp:727
EVT_MENU(ID_COMPARE_PROJECT_BRANCHES, KICAD_MANAGER_FRAME::OnCompareProjectBranches) KICAD_MANAGER_FRAME
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:521
Declaration of the NL_3D_VIEWER_PLUGIN class.
ANTIALIASING_MODE
Anti-aliasing options.
#define _HKI(x)
Definition page_info.cpp:40
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
T * GetAppSettings(const char *aFilename)
Functors that can be used to figure out how the action controls should be displayed in the UI and if ...
wxString result
Test unit parsing edge cases and error handling.
Definition of file extensions used in Kicad.
void SetAuiPaneSize(wxAuiManager &aManager, wxAuiPaneInfo &aPane, int aWidth, int aHeight)
Sets the size of an AUI pane, working around http://trac.wxwidgets.org/ticket/13180.