Browse Source

ADDED: Common 3d image export and file size choice

Instead of two actions for exporting to two different file formats,
unify into a single Export Image command with the format chosen based on
file extension/selected format in save dialog.

Also allow arbitrary image size export

Fixes https://gitlab.com/kicad/code/kicad/-/issues/3689

Fixes https://gitlab.com/kicad/code/kicad/-/issues/4693
pull/18/head
Seth Hillbrand 4 months ago
parent
commit
e6e44d08fc
  1. 209
      3d-viewer/3d_canvas/eda_3d_canvas.cpp
  2. 2
      3d-viewer/3d_canvas/eda_3d_canvas.h
  3. 3
      3d-viewer/3d_viewer/3d_menubar.cpp
  4. 350
      3d-viewer/3d_viewer/eda_3d_viewer_frame.cpp
  5. 92
      3d-viewer/3d_viewer/eda_3d_viewer_frame.h
  6. 18
      3d-viewer/3d_viewer/tools/eda_3d_actions.cpp
  7. 3
      3d-viewer/3d_viewer/tools/eda_3d_actions.h
  8. 26
      3d-viewer/3d_viewer/tools/eda_3d_controller.cpp
  9. 1
      3d-viewer/CMakeLists.txt
  10. 396
      3d-viewer/dialogs/dialog_export_3d_image.cpp
  11. 94
      3d-viewer/dialogs/dialog_export_3d_image.h

209
3d-viewer/3d_canvas/eda_3d_canvas.cpp

@ -600,6 +600,215 @@ void EDA_3D_CANVAS::DoRePaint()
}
void EDA_3D_CANVAS::RenderToFrameBuffer( unsigned char* buffer, int width, int height )
{
if( m_is_currently_painting.test_and_set() )
return;
// Validate input parameters
if( !buffer || width <= 0 || height <= 0 )
{
m_is_currently_painting.clear();
return;
}
// Because the board to draw is handled by the parent viewer frame,
// ensure this parent is still alive
if( !GetParent() || !GetParent()->GetParent() || !GetParent()->GetParent()->IsShownOnScreen() )
{
m_is_currently_painting.clear();
return;
}
wxString err_messages;
int64_t start_time = GetRunningMicroSecs();
GL_CONTEXT_MANAGER* gl_mgr = Pgm().GetGLContextManager();
// Create OpenGL context if needed
if( m_glRC == nullptr )
m_glRC = gl_mgr->CreateCtx( this );
if( m_glRC == nullptr )
{
wxLogError( _( "OpenGL context creation error" ) );
m_is_currently_painting.clear();
return;
}
gl_mgr->LockCtx( m_glRC, this );
// Set up framebuffer objects for off-screen rendering
GLuint framebuffer = 0;
GLuint colorTexture = 0;
GLuint depthBuffer = 0;
GLint oldFramebuffer = 0;
GLint oldViewport[4];
// Save current state
glGetIntegerv( GL_FRAMEBUFFER_BINDING, &oldFramebuffer );
glGetIntegerv( GL_VIEWPORT, oldViewport );
// Create and bind framebuffer
glGenFramebuffers( 1, &framebuffer );
glBindFramebuffer( GL_FRAMEBUFFER, framebuffer );
// Create color texture attachment
glGenTextures( 1, &colorTexture );
glBindTexture( GL_TEXTURE_2D, colorTexture );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0 );
// Create depth renderbuffer attachment
glGenRenderbuffers( 1, &depthBuffer );
glBindRenderbuffer( GL_RENDERBUFFER, depthBuffer );
glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height );
glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer );
auto resetState = std::unique_ptr<void, std::function<void(void*)>>(
reinterpret_cast<void*>(1),
[&](void*) {
glBindFramebuffer( GL_FRAMEBUFFER, oldFramebuffer );
glViewport( oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3] );
glDeleteFramebuffers( 1, &framebuffer );
glDeleteTextures( 1, &colorTexture );
glDeleteRenderbuffers( 1, &depthBuffer );
gl_mgr->UnlockCtx( m_glRC );
m_is_currently_painting.clear();
}
);
// Check framebuffer completeness
GLenum framebufferStatus = glCheckFramebufferStatus( GL_FRAMEBUFFER );
if( framebufferStatus != GL_FRAMEBUFFER_COMPLETE )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::RenderToFrameBuffer Framebuffer incomplete: 0x%04X" ),
framebufferStatus );
return;
}
// Set viewport for off-screen rendering
glViewport( 0, 0, width, height );
// Set window size for camera and rendering
wxSize clientSize( width, height );
const bool windows_size_changed = m_camera.SetCurWindowSize( clientSize );
// Initialize OpenGL if needed
if( !m_is_opengl_initialized )
{
if( !initializeOpenGL() )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::RenderToFrameBuffer OpenGL initialization failed." ) );
return;
}
if( !m_is_opengl_version_supported )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::RenderToFrameBuffer OpenGL version not supported." ) );
}
}
if( !m_is_opengl_version_supported )
{
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
glClear( GL_COLOR_BUFFER_BIT );
// Read black screen to buffer
glReadPixels( 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer );
return;
}
// Handle raytracing/OpenGL renderer selection
if( !m_opengl_supports_raytracing )
{
m_3d_render = m_3d_render_opengl;
m_render_raytracing_was_requested = false;
m_boardAdapter.m_Cfg->m_Render.engine = RENDER_ENGINE::OPENGL;
}
if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
{
const bool was_camera_changed = m_camera.ParametersChanged();
if( ( m_mouse_is_moving || m_camera_is_moving || was_camera_changed || windows_size_changed )
&& m_render_raytracing_was_requested )
{
m_render_raytracing_was_requested = false;
m_3d_render = m_3d_render_opengl;
}
}
// Handle camera animation (simplified for off-screen rendering)
float curtime_delta_s = 0.0f;
if( m_camera_is_moving )
{
const int64_t curtime_delta = GetRunningMicroSecs() - m_strtime_camera_movement;
curtime_delta_s = ( curtime_delta / 1e6 ) * m_camera_moving_speed;
m_camera.Interpolate( curtime_delta_s );
if( curtime_delta_s > 1.0f )
{
m_render_pivot = false;
m_camera_is_moving = false;
m_mouse_was_moved = true;
}
}
// Perform the actual rendering
bool requested_redraw = false;
if( m_3d_render )
{
try
{
m_3d_render->SetCurWindowSize( clientSize );
bool reloadRaytracingForCalculations = false;
if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL
&& m_3d_render_opengl->IsReloadRequestPending() )
{
reloadRaytracingForCalculations = true;
}
requested_redraw = m_3d_render->Redraw( false, nullptr, nullptr );
if( reloadRaytracingForCalculations )
m_3d_render_raytracing->Reload( nullptr, nullptr, true );
}
catch( std::runtime_error& )
{
m_is_opengl_version_supported = false;
m_opengl_supports_raytracing = false;
m_is_opengl_initialized = false;
return;
}
}
// Read pixels from framebuffer to the provided buffer
// Note: This reads RGB format. Adjust format as needed.
glReadPixels( 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer );
// Check for OpenGL errors
GLenum error = glGetError();
if( error != GL_NO_ERROR )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::RenderToFrameBuffer OpenGL error: 0x%04X" ), error );
err_messages += wxString::Format( _( "OpenGL error during off-screen rendering: 0x%04X\n" ), error );
}
// Reset camera parameters changed flag
m_camera.ParametersChanged();
if( !err_messages.IsEmpty() )
wxLogMessage( err_messages );
}
void EDA_3D_CANVAS::SetEventDispatcher( TOOL_DISPATCHER* aEventDispatcher )
{
m_eventDispatcher = aEventDispatcher;

2
3d-viewer/3d_canvas/eda_3d_canvas.h

@ -218,6 +218,8 @@ public:
*/
void DoRePaint();
void RenderToFrameBuffer( unsigned char* aBuffer, int aWidth, int aHeight );
void OnCloseWindow( wxCloseEvent& event );
private:

3
3d-viewer/3d_viewer/3d_menubar.cpp

@ -48,8 +48,7 @@ void EDA_3D_VIEWER_FRAME::doReCreateMenuBar()
//
ACTION_MENU* fileMenu = new ACTION_MENU( false, tool );
fileMenu->Add( EDA_3D_ACTIONS::exportAsPNG );
fileMenu->Add( EDA_3D_ACTIONS::exportAsJPEG );
fileMenu->Add( EDA_3D_ACTIONS::exportImage );
fileMenu->AppendSeparator();
fileMenu->AddClose( _( "3D Viewer" ) );

350
3d-viewer/3d_viewer/eda_3d_viewer_frame.cpp

@ -34,6 +34,7 @@
#include "dialogs/appearance_controls_3D.h"
#include <dialogs/eda_view_switcher.h>
#include <eda_3d_viewer_settings.h>
#include <3d_rendering/raytracing/render_3d_raytrace_ram.h>
#include <3d_viewer_id.h>
#include <3d_viewer/tools/eda_3d_actions.h>
#include <3d_viewer/tools/eda_3d_controller.h>
@ -655,101 +656,326 @@ void EDA_3D_VIEWER_FRAME::OnDarkModeToggle()
void EDA_3D_VIEWER_FRAME::TakeScreenshot( EDA_3D_VIEWER_EXPORT_FORMAT aFormat )
{
wxString fullFileName;
bool fmt_is_jpeg = false;
if( aFormat == EDA_3D_VIEWER_EXPORT_FORMAT::JPEG )
fmt_is_jpeg = true;
wxString fullFileName;
if( aFormat != EDA_3D_VIEWER_EXPORT_FORMAT::CLIPBOARD )
{
// Remember path between saves during this session only.
const wxString wildcard =
fmt_is_jpeg ? FILEEXT::JpegFileWildcard() : FILEEXT::PngFileWildcard();
const wxString ext = fmt_is_jpeg ? FILEEXT::JpegFileExtension : FILEEXT::PngFileExtension;
if( !getExportFileName( aFormat, fullFileName ) )
return;
}
// First time path is set to the project path.
if( !m_defaultSaveScreenshotFileName.IsOk() )
m_defaultSaveScreenshotFileName = Parent()->Prj().GetProjectFullName();
wxImage screenshotImage = captureCurrentViewScreenshot();
m_defaultSaveScreenshotFileName.SetExt( ext );
if( screenshotImage.IsOk() )
{
saveOrCopyImage( screenshotImage, aFormat, fullFileName );
}
}
wxFileDialog dlg( this, _( "3D Image File Name" ),
m_defaultSaveScreenshotFileName.GetPath(),
m_defaultSaveScreenshotFileName.GetFullName(), wildcard,
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
if( dlg.ShowModal() == wxID_CANCEL )
return;
wxImage EDA_3D_VIEWER_FRAME::captureCurrentViewScreenshot()
{
// Ensure we have the latest 3D view (remember 3D view is buffered)
// Also ensure any highlighted item is not highlighted when creating screen shot
EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& cfg = m_boardAdapter.m_Cfg->m_Render;
bool original_highlight = cfg.highlight_on_rollover;
cfg.highlight_on_rollover = false;
m_defaultSaveScreenshotFileName = dlg.GetPath();
m_canvas->DoRePaint(); // init first buffer
m_canvas->DoRePaint(); // init second buffer
if( m_defaultSaveScreenshotFileName.GetExt().IsEmpty() )
m_defaultSaveScreenshotFileName.SetExt( ext );
wxImage screenshotImage;
fullFileName = m_defaultSaveScreenshotFileName.GetFullPath();
if( m_canvas )
{
// Build image from the 3D buffer
wxWindowUpdateLocker noUpdates( this );
m_canvas->GetScreenshot( screenshotImage );
}
wxFileName fn = fullFileName;
// Restore highlight setting
cfg.highlight_on_rollover = original_highlight;
if( !fn.IsDirWritable() )
{
wxString msg;
return screenshotImage;
}
msg.Printf( _( "Insufficient permissions to save file '%s'." ), fullFileName );
wxMessageBox( msg, _( "Error" ), wxOK | wxICON_ERROR, this );
void EDA_3D_VIEWER_FRAME::ExportImage( EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxSize& aSize )
{
wxString fullFileName;
if( aFormat != EDA_3D_VIEWER_EXPORT_FORMAT::CLIPBOARD )
{
if( !getExportFileName( aFormat, fullFileName ) )
return;
}
}
wxImage screenshotImage = captureScreenshot( aSize );
// Be sure the screen area destroyed by the file dialog is redrawn
// before making a screen copy.
// Without this call, under Linux the screen refresh is made to late.
wxYield();
if( screenshotImage.IsOk() )
{
saveOrCopyImage( screenshotImage, aFormat, fullFileName );
}
}
// Be sure we have the latest 3D view (remember 3D view is buffered)
// Also ensure any highlighted item is not highlighted when creating screen shot
EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& cfg = m_boardAdapter.m_Cfg->m_Render;
bool allow_highlight = cfg.highlight_on_rollover;
cfg.highlight_on_rollover = false;
m_canvas->DoRePaint(); // init first buffer
m_canvas->DoRePaint(); // init second buffer
bool EDA_3D_VIEWER_FRAME::getExportFileName( EDA_3D_VIEWER_EXPORT_FORMAT& aFormat, wxString& fullFileName )
{
// Create combined wildcard for both formats
const wxString wildcard = FILEEXT::JpegFileWildcard() + "|" + FILEEXT::PngFileWildcard();
// Build image from the 3D buffer
wxWindowUpdateLocker noUpdates( this );
if( !m_defaultSaveScreenshotFileName.IsOk() )
m_defaultSaveScreenshotFileName = Parent()->Prj().GetProjectFullName();
wxImage screenshotImage;
// Set default extension based on current format
const wxString defaultExt = ( aFormat == EDA_3D_VIEWER_EXPORT_FORMAT::JPEG ) ?
FILEEXT::JpegFileExtension : FILEEXT::PngFileExtension;
m_defaultSaveScreenshotFileName.SetExt( defaultExt );
if( m_canvas )
m_canvas->GetScreenshot( screenshotImage );
wxFileDialog dlg( this, _( "3D Image File Name" ),
m_defaultSaveScreenshotFileName.GetPath(),
m_defaultSaveScreenshotFileName.GetFullName(), wildcard,
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
cfg.highlight_on_rollover = allow_highlight;
// Set initial filter index based on current format
dlg.SetFilterIndex( ( aFormat == EDA_3D_VIEWER_EXPORT_FORMAT::JPEG ) ? 0 : 1 );
if( aFormat == EDA_3D_VIEWER_EXPORT_FORMAT::CLIPBOARD )
if( dlg.ShowModal() == wxID_CANCEL )
return false;
m_defaultSaveScreenshotFileName = dlg.GetPath();
// Determine format based on file extension first
wxString fileExt = m_defaultSaveScreenshotFileName.GetExt().Lower();
EDA_3D_VIEWER_EXPORT_FORMAT detectedFormat;
bool formatDetected = false;
if( fileExt == wxT("jpg") || fileExt == wxT("jpeg") )
{
detectedFormat = EDA_3D_VIEWER_EXPORT_FORMAT::JPEG;
formatDetected = true;
}
else if( fileExt == wxT("png") )
{
wxBitmap bitmap( screenshotImage );
detectedFormat = EDA_3D_VIEWER_EXPORT_FORMAT::PNG;
formatDetected = true;
}
wxLogNull doNotLog; // disable logging of failed clipboard actions
// If format can't be determined from extension, use dropdown selection
if( !formatDetected )
{
int filterIndex = dlg.GetFilterIndex();
detectedFormat = ( filterIndex == 0 ) ? EDA_3D_VIEWER_EXPORT_FORMAT::JPEG :
EDA_3D_VIEWER_EXPORT_FORMAT::PNG;
if( wxTheClipboard->Open() )
{
wxBitmapDataObject* dobjBmp = new wxBitmapDataObject( bitmap );
// Append appropriate extension
const wxString ext = ( detectedFormat == EDA_3D_VIEWER_EXPORT_FORMAT::JPEG ) ?
FILEEXT::JpegFileExtension : FILEEXT::PngFileExtension;
m_defaultSaveScreenshotFileName.SetExt( ext );
}
// Update the format parameter
aFormat = detectedFormat;
// Check directory permissions using the updated filename
wxFileName fn = m_defaultSaveScreenshotFileName;
if( !fn.IsDirWritable() )
{
wxString msg;
msg.Printf( _( "Insufficient permissions to save file '%s'." ),
m_defaultSaveScreenshotFileName.GetFullPath() );
wxMessageBox( msg, _( "Error" ), wxOK | wxICON_ERROR, this );
return false;
}
fullFileName = m_defaultSaveScreenshotFileName.GetFullPath();
return true;
}
wxImage EDA_3D_VIEWER_FRAME::captureScreenshot( const wxSize& aSize )
{
TRACK_BALL camera = m_trackBallCamera;
camera.SetCurWindowSize( aSize );
EDA_3D_VIEWER_SETTINGS* cfg = GetAppSettings<EDA_3D_VIEWER_SETTINGS>( "3d_viewer" );
EDA_3D_VIEWER_SETTINGS* backupCfg = m_boardAdapter.m_Cfg;
auto configRestorer = std::unique_ptr<void, std::function<void(void*)>>(
reinterpret_cast<void*>(1),
[&](void*) { m_boardAdapter.m_Cfg = backupCfg; }
);
if( cfg )
m_boardAdapter.m_Cfg = cfg;
if( cfg && cfg->m_Render.engine == RENDER_ENGINE::RAYTRACING )
return captureRaytracingScreenshot( m_boardAdapter, camera, aSize );
else
return captureOpenGLScreenshot( m_boardAdapter, camera, aSize );
}
void EDA_3D_VIEWER_FRAME::setupRenderingConfig( BOARD_ADAPTER& aAdapter )
{
EDA_3D_VIEWER_SETTINGS* cfg = GetAppSettings<EDA_3D_VIEWER_SETTINGS>( "3d_viewer" );
if( cfg )
aAdapter.m_Cfg = cfg;
}
wxImage EDA_3D_VIEWER_FRAME::captureRaytracingScreenshot( BOARD_ADAPTER& aAdapter, TRACK_BALL& aCamera, const wxSize& aSize )
{
BOARD_ADAPTER tempadapter;
tempadapter.SetBoard( GetBoard() );
tempadapter.m_Cfg = aAdapter.m_Cfg;
tempadapter.InitSettings( nullptr, nullptr );
tempadapter.Set3dCacheManager( aAdapter.Get3dCacheManager() );
RENDER_3D_RAYTRACE_RAM raytrace( tempadapter, aCamera );
raytrace.SetCurWindowSize( aSize );
while( raytrace.Redraw( false, nullptr, nullptr ) );
uint8_t* rgbaBuffer = raytrace.GetBuffer();
wxSize realSize = raytrace.GetRealBufferSize();
if( !wxTheClipboard->SetData( dobjBmp ) )
wxMessageBox( _( "Failed to copy image to clipboard" ) );
if( !rgbaBuffer )
return wxImage();
wxTheClipboard->Flush(); /* the data in clipboard will stay
* available after the application exits */
wxTheClipboard->Close();
return convertRGBAToImage( rgbaBuffer, realSize );
}
wxImage EDA_3D_VIEWER_FRAME::convertRGBAToImage( uint8_t* aRGBABuffer, const wxSize& aRealSize )
{
const unsigned int wxh = aRealSize.x * aRealSize.y;
unsigned char* rgbBuffer = (unsigned char*) malloc( wxh * 3 );
unsigned char* alphaBuffer = (unsigned char*) malloc( wxh );
unsigned char* rgbaPtr = aRGBABuffer;
unsigned char* rgbPtr = rgbBuffer;
unsigned char* alphaPtr = alphaBuffer;
for( int y = 0; y < aRealSize.y; y++ )
{
for( int x = 0; x < aRealSize.x; x++ )
{
rgbPtr[0] = rgbaPtr[0];
rgbPtr[1] = rgbaPtr[1];
rgbPtr[2] = rgbaPtr[2];
alphaPtr[0] = rgbaPtr[3];
rgbaPtr += 4;
rgbPtr += 3;
alphaPtr += 1;
}
}
wxImage screenshotImage;
screenshotImage.Create( aRealSize );
screenshotImage.SetData( rgbBuffer );
screenshotImage.SetAlpha( alphaBuffer );
return screenshotImage.Mirror( false );
}
wxImage EDA_3D_VIEWER_FRAME::captureOpenGLScreenshot( BOARD_ADAPTER& aAdapter, TRACK_BALL& aCamera, const wxSize& aSize )
{
EDA_3D_VIEWER_SETTINGS* cfg = aAdapter.m_Cfg;
ANTIALIASING_MODE aaMode = cfg ? static_cast<ANTIALIASING_MODE>( cfg->m_Render.opengl_AA_mode )
: ANTIALIASING_MODE::AA_NONE;
wxFrame temp( this, wxID_ANY, wxEmptyString, wxDefaultPosition, aSize, wxFRAME_NO_TASKBAR );
temp.Hide();
BOARD_ADAPTER tempadapter;
tempadapter.SetBoard( GetBoard() );
tempadapter.m_Cfg = aAdapter.m_Cfg;
tempadapter.InitSettings( nullptr, nullptr );
tempadapter.Set3dCacheManager( aAdapter.Get3dCacheManager() );
auto canvas = std::make_unique<EDA_3D_CANVAS>( &temp,
OGL_ATT_LIST::GetAttributesList( aaMode, true ),
tempadapter, aCamera,
aAdapter.Get3dCacheManager() );
canvas->SetSize( aSize );
configureCanvas( canvas, cfg );
wxWindowUpdateLocker noUpdates( this );
// Temporarily disable highlight during screenshot
EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& renderCfg = aAdapter.m_Cfg->m_Render;
bool original_highlight = renderCfg.highlight_on_rollover;
bool original_navigator = renderCfg.show_navigator;
renderCfg.show_navigator = false;
renderCfg.highlight_on_rollover = false;
std::vector<unsigned char> buffer(aSize.x * aSize.y * 4); // RGBA format
canvas->RenderToFrameBuffer( buffer.data(), aSize.x, aSize.y );
wxImage result = convertRGBAToImage( buffer.data(), aSize );
// Restore highlight setting
renderCfg.highlight_on_rollover = original_highlight;
renderCfg.show_navigator = original_navigator;
return result;
}
void EDA_3D_VIEWER_FRAME::configureCanvas( std::unique_ptr<EDA_3D_CANVAS>& aCanvas, EDA_3D_VIEWER_SETTINGS* aCfg )
{
if( aCfg )
{
aCanvas->SetAnimationEnabled( aCfg->m_Camera.animation_enabled );
aCanvas->SetMovingSpeedMultiplier( aCfg->m_Camera.moving_speed_multiplier );
aCanvas->SetProjectionMode( aCfg->m_Camera.projection_mode );
}
aCanvas->SetVcSettings( EDA_DRAW_PANEL_GAL::GetVcSettings() );
}
void EDA_3D_VIEWER_FRAME::saveOrCopyImage( const wxImage& aScreenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString& aFullFileName )
{
if( aFormat == EDA_3D_VIEWER_EXPORT_FORMAT::CLIPBOARD )
{
copyImageToClipboard( aScreenshotImage );
}
else
{
if( !screenshotImage.SaveFile( fullFileName,
fmt_is_jpeg ? wxBITMAP_TYPE_JPEG : wxBITMAP_TYPE_PNG ) )
wxMessageBox( _( "Can't save file" ) );
saveImageToFile( aScreenshotImage, aFormat, aFullFileName );
}
}
void EDA_3D_VIEWER_FRAME::copyImageToClipboard( const wxImage& aScreenshotImage )
{
wxBitmap bitmap( aScreenshotImage );
wxLogNull doNotLog;
if( wxTheClipboard->Open() )
{
wxBitmapDataObject* dobjBmp = new wxBitmapDataObject( bitmap );
screenshotImage.Destroy();
if( !wxTheClipboard->SetData( dobjBmp ) )
wxMessageBox( _( "Failed to copy image to clipboard" ) );
wxTheClipboard->Flush();
wxTheClipboard->Close();
}
}
void EDA_3D_VIEWER_FRAME::saveImageToFile( const wxImage& aScreenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString& aFullFileName )
{
bool fmt_is_jpeg = ( aFormat == EDA_3D_VIEWER_EXPORT_FORMAT::JPEG );
if( !aScreenshotImage.SaveFile( aFullFileName, fmt_is_jpeg ? wxBITMAP_TYPE_JPEG : wxBITMAP_TYPE_PNG ) )
{
wxMessageBox( _( "Can't save file" ) );
}
}

92
3d-viewer/3d_viewer/eda_3d_viewer_frame.h

@ -60,6 +60,7 @@ enum EDA_3D_VIEWER_STATUSBAR
enum class EDA_3D_VIEWER_EXPORT_FORMAT
{
CLIPBOARD,
IMAGE,
PNG,
JPEG
};
@ -136,6 +137,13 @@ public:
*/
void TakeScreenshot( EDA_3D_VIEWER_EXPORT_FORMAT aFormat );
/**
* Export 3D viewer image to file or clipboard
* @param aFormat - Export format (JPEG, PNG, or CLIPBOARD)
* @param aSize - Size of the exported image
*/
void ExportImage( EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxSize& aSize );
protected:
void setupUIConditions() override;
@ -174,6 +182,90 @@ private:
void applySettings( EDA_3D_VIEWER_SETTINGS* aSettings );
/**
* Get export filename through file dialog
* @param aFormat - [in/out] Export format to determine default file extension and wildcard.
* Will be updated to the selected format.
* @param fullFileName - [out] Full path of selected file
* @return true if filename was successfully obtained, false if cancelled
*/
bool getExportFileName( EDA_3D_VIEWER_EXPORT_FORMAT& aFormat, wxString& fullFileName );
/**
* Capture screenshot using appropriate rendering method
* @param aSize - Size of the screenshot
* @return wxImage containing the screenshot
*/
wxImage captureScreenshot( const wxSize& aSize );
/**
* Setup rendering configuration for screenshot capture
* @param adapter - Board adapter to configure
*/
void setupRenderingConfig( BOARD_ADAPTER& adapter );
/**
* Capture screenshot of the current view using the configured renderer.
* This function handles both ray tracing and OpenGL rendering methods.
* @return wxImage containing the screenshot of the current view.
*/
wxImage captureCurrentViewScreenshot();
/**
* Capture screenshot using raytracing renderer
* @param adapter - Configured board adapter
* @param camera - Camera settings
* @param aSize - Size of the screenshot
* @return wxImage containing the screenshot
*/
wxImage captureRaytracingScreenshot( BOARD_ADAPTER& adapter, TRACK_BALL& camera, const wxSize& aSize );
/**
* Convert RGBA buffer to wxImage format
* @param rgbaBuffer - Source RGBA buffer
* @param realSize - Size of the buffer
* @return wxImage with RGB data and alpha channel
*/
wxImage convertRGBAToImage( uint8_t* rgbaBuffer, const wxSize& realSize );
/**
* Capture screenshot using OpenGL renderer
* @param adapter - Configured board adapter
* @param camera - Camera settings
* @param aSize - Size of the screenshot
* @return wxImage containing the screenshot
*/
wxImage captureOpenGLScreenshot( BOARD_ADAPTER& adapter, TRACK_BALL& camera, const wxSize& aSize );
/**
* Configure canvas settings for screenshot capture
* @param canvas - Canvas to configure
* @param cfg - Configuration settings (can be nullptr)
*/
void configureCanvas( std::unique_ptr<EDA_3D_CANVAS>& canvas, EDA_3D_VIEWER_SETTINGS* cfg );
/**
* Save image to file or copy to clipboard based on format
* @param screenshotImage - Image to save/copy
* @param aFormat - Export format
* @param fullFileName - Full path for file save (ignored for clipboard)
*/
void saveOrCopyImage( const wxImage& screenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString& fullFileName );
/**
* Copy image to system clipboard
* @param screenshotImage - Image to copy
*/
void copyImageToClipboard( const wxImage& screenshotImage );
/**
* Save image to file
* @param screenshotImage - Image to save
* @param aFormat - Export format (JPEG or PNG)
* @param fullFileName - Full path of target file
*/
void saveImageToFile( const wxImage& screenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString& fullFileName );
private:
wxFileName m_defaultSaveScreenshotFileName;

18
3d-viewer/3d_viewer/tools/eda_3d_actions.cpp

@ -65,21 +65,13 @@ TOOL_ACTION EDA_3D_ACTIONS::copyToClipboard( TOOL_ACTION_ARGS()
.Icon( BITMAPS::copy )
.Parameter<EDA_3D_VIEWER_EXPORT_FORMAT>( EDA_3D_VIEWER_EXPORT_FORMAT::CLIPBOARD ) );
TOOL_ACTION EDA_3D_ACTIONS::exportAsPNG( TOOL_ACTION_ARGS()
.Name( "3DViewer.Control.exportAsPNG" )
TOOL_ACTION EDA_3D_ACTIONS::exportImage( TOOL_ACTION_ARGS()
.Name( "3DViewer.Control.exportImage" )
.Scope( AS_GLOBAL )
.FriendlyName( _( "Export Current View as PNG..." ) )
.Tooltip( _( "Export the Current View as a PNG image" ) )
.Icon( BITMAPS::export_png )
.Parameter<EDA_3D_VIEWER_EXPORT_FORMAT>( EDA_3D_VIEWER_EXPORT_FORMAT::PNG ) );
TOOL_ACTION EDA_3D_ACTIONS::exportAsJPEG( TOOL_ACTION_ARGS()
.Name( "3DViewer.Control.exportAsJPEG" )
.Scope( AS_GLOBAL )
.FriendlyName( _( "Export Current View as JPEG..." ) )
.Tooltip( _( "Export the Current View as a JPEG image" ) )
.FriendlyName( _( "Export Image..." ) )
.Tooltip( _( "Export the Current View as an image file" ) )
.Icon( BITMAPS::export_file )
.Parameter<EDA_3D_VIEWER_EXPORT_FORMAT>( EDA_3D_VIEWER_EXPORT_FORMAT::JPEG ) );
.Parameter<EDA_3D_VIEWER_EXPORT_FORMAT>( EDA_3D_VIEWER_EXPORT_FORMAT::IMAGE ) );
TOOL_ACTION EDA_3D_ACTIONS::pivotCenter( TOOL_ACTION_ARGS()
.Name( "3DViewer.Control.pivotCenter" )

3
3d-viewer/3d_viewer/tools/eda_3d_actions.h

@ -46,8 +46,7 @@ public:
static TOOL_ACTION toggleRaytacing;
static TOOL_ACTION copyToClipboard;
static TOOL_ACTION exportAsPNG;
static TOOL_ACTION exportAsJPEG;
static TOOL_ACTION exportImage;
static TOOL_ACTION pivotCenter;
static TOOL_ACTION rotateXCW;

26
3d-viewer/3d_viewer/tools/eda_3d_controller.cpp

@ -30,6 +30,7 @@
#include <kiface_base.h>
#include <tools/eda_3d_controller.h>
#include <tools/eda_3d_actions.h>
#include <dialogs/dialog_export_3d_image.h>
#include <dialogs/panel_preview_3d_model.h>
#include <dialogs/appearance_controls_3D.h>
#include <3d_rendering/opengl/render_3d_opengl.h>
@ -374,8 +375,26 @@ int EDA_3D_CONTROLLER::ExportImage( const TOOL_EVENT& aEvent )
{
EDA_BASE_FRAME* frame = dynamic_cast<EDA_BASE_FRAME*>( m_toolMgr->GetToolHolder() );
if( frame && frame->GetFrameType() == FRAME_PCB_DISPLAY3D )
static_cast<EDA_3D_VIEWER_FRAME*>( frame )->TakeScreenshot( aEvent.Parameter<EDA_3D_VIEWER_EXPORT_FORMAT>() );
if( !frame || frame->GetFrameType() != FRAME_PCB_DISPLAY3D )
return 0;
EDA_3D_VIEWER_FRAME* viewer = static_cast<EDA_3D_VIEWER_FRAME*>( frame );
EDA_3D_VIEWER_EXPORT_FORMAT fmt = aEvent.Parameter<EDA_3D_VIEWER_EXPORT_FORMAT>();
wxSize currentSize = viewer->GetCanvas()->GetClientSize();
if( fmt == EDA_3D_VIEWER_EXPORT_FORMAT::CLIPBOARD )
{
viewer->ExportImage( fmt, currentSize );
return 0;
}
static wxSize lastSize( viewer->GetCanvas()->GetClientSize() );
static EDA_3D_VIEWER_EXPORT_FORMAT lastFormat = EDA_3D_VIEWER_EXPORT_FORMAT::PNG;
DIALOG_EXPORT_3D_IMAGE dlg( viewer, currentSize );
if( dlg.ShowModal() == wxID_OK )
viewer->ExportImage( lastFormat, dlg.GetSize() );
return 0;
}
@ -390,8 +409,7 @@ void EDA_3D_CONTROLLER::setTransitions()
Go( &EDA_3D_CONTROLLER::ReloadBoard, EDA_3D_ACTIONS::reloadBoard.MakeEvent() );
Go( &EDA_3D_CONTROLLER::ToggleRaytracing, EDA_3D_ACTIONS::toggleRaytacing.MakeEvent() );
Go( &EDA_3D_CONTROLLER::ExportImage, EDA_3D_ACTIONS::copyToClipboard.MakeEvent() );
Go( &EDA_3D_CONTROLLER::ExportImage, EDA_3D_ACTIONS::exportAsPNG.MakeEvent() );
Go( &EDA_3D_CONTROLLER::ExportImage, EDA_3D_ACTIONS::exportAsJPEG.MakeEvent() );
Go( &EDA_3D_CONTROLLER::ExportImage, EDA_3D_ACTIONS::exportImage.MakeEvent() );
// Pan control
Go( &EDA_3D_CONTROLLER::PanControl, ACTIONS::panUp.MakeEvent() );

1
3d-viewer/CMakeLists.txt

@ -98,6 +98,7 @@ set(3D-VIEWER_SRCS
dialogs/dialog_select_3d_model.cpp
dialogs/panel_preview_3d_model_base.cpp
dialogs/panel_preview_3d_model.cpp
dialogs/dialog_export_3d_image.cpp
dialogs/panel_3D_display_options.cpp
dialogs/panel_3D_display_options_base.cpp
dialogs/panel_3D_opengl_options.cpp

396
3d-viewer/dialogs/dialog_export_3d_image.cpp

@ -0,0 +1,396 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/gpl-3.0.html
* or you may search the http://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "dialog_export_3d_image.h"
#include <bitmaps/bitmap_types.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/spinctrl.h>
#include <wx/choice.h>
#include <wx/bmpbuttn.h>
#include <wx/statline.h>
DIALOG_EXPORT_3D_IMAGE::DIALOG_EXPORT_3D_IMAGE( wxWindow* aParent, const wxSize& aSize ) :
DIALOG_SHIM( aParent, wxID_ANY, _( "Export 3D View" ), wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE ),
m_originalSize( aSize ),
m_width( aSize.GetWidth() ),
m_height( aSize.GetHeight() ),
m_xResolution( 300.0 ),
m_yResolution( 300.0 ),
m_lockAspectRatio( true ),
m_sizeUnits( SIZE_UNITS::PIXELS ),
m_resolutionUnits( RESOLUTION_UNITS::PIXELS_PER_INCH )
{
m_aspectRatio = static_cast<double>(m_width) / static_cast<double>(m_height);
wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
// Image Size section
wxStaticText* imageSizeHeader = new wxStaticText( this, wxID_ANY, _( "Image Size" ) );
imageSizeHeader->SetFont( imageSizeHeader->GetFont().Bold() );
mainSizer->Add( imageSizeHeader, 0, wxLEFT | wxTOP, 10 );
wxFlexGridSizer* sizeGrid = new wxFlexGridSizer( 2, 4, 5, 5 );
sizeGrid->AddGrowableCol( 1 );
// Width row
sizeGrid->Add( new wxStaticText( this, wxID_ANY, _( "Width:" ) ), 0, wxALIGN_CENTER_VERTICAL );
m_spinWidth = new wxSpinCtrlDouble( this, wxID_ANY );
m_spinWidth->SetRange( 1, 50000 );
m_spinWidth->SetDigits( 0 );
sizeGrid->Add( m_spinWidth, 1, wxEXPAND );
// Lock button - will span 2 rows
m_lockButton = new wxBitmapButton( this, wxID_ANY, KiBitmapBundle( BITMAPS::locked ) );
sizeGrid->Add( m_lockButton, 0, wxALIGN_CENTER | wxALL, 2 );
// Size units choice
m_choiceSizeUnits = new wxChoice( this, wxID_ANY );
m_choiceSizeUnits->Append( _( "pixels" ) );
m_choiceSizeUnits->Append( _( "%" ) );
m_choiceSizeUnits->Append( _( "mm" ) );
m_choiceSizeUnits->Append( _( "in" ) );
m_choiceSizeUnits->SetSelection( 0 ); // pixels
sizeGrid->Add( m_choiceSizeUnits, 0, wxEXPAND );
// Height row
sizeGrid->Add( new wxStaticText( this, wxID_ANY, _( "Height:" ) ), 0, wxALIGN_CENTER_VERTICAL );
m_spinHeight = new wxSpinCtrlDouble( this, wxID_ANY );
m_spinHeight->SetRange( 1, 50000 );
m_spinHeight->SetDigits( 0 );
sizeGrid->Add( m_spinHeight, 1, wxEXPAND );
sizeGrid->AddSpacer( 0 ); // Empty space where lock button is
sizeGrid->AddSpacer( 0 ); // Empty space for units column
mainSizer->Add( sizeGrid, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 10 );
// Pixel size display
m_pixelSizeLabel = new wxStaticText( this, wxID_ANY, wxEmptyString );
m_pixelSizeLabel->SetFont( m_pixelSizeLabel->GetFont().Smaller() );
mainSizer->Add( m_pixelSizeLabel, 0, wxLEFT | wxTOP, 10 );
// Separator
wxStaticLine* line = new wxStaticLine( this );
mainSizer->Add( line, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 10 );
// Resolution section
wxStaticText* resolutionHeader = new wxStaticText( this, wxID_ANY, _( "Resolution" ) );
resolutionHeader->SetFont( resolutionHeader->GetFont().Bold() );
mainSizer->Add( resolutionHeader, 0, wxLEFT | wxTOP, 10 );
wxFlexGridSizer* resGrid = new wxFlexGridSizer( 2, 3, 5, 5 );
resGrid->AddGrowableCol( 1 );
// X Resolution row
resGrid->Add( new wxStaticText( this, wxID_ANY, _( "X resolution:" ) ), 0, wxALIGN_CENTER_VERTICAL );
m_spinXResolution = new wxSpinCtrlDouble( this, wxID_ANY );
m_spinXResolution->SetRange( 1, 10000 );
m_spinXResolution->SetDigits( 3 );
resGrid->Add( m_spinXResolution, 1, wxEXPAND );
// Resolution units choice
m_choiceResolutionUnits = new wxChoice( this, wxID_ANY );
m_choiceResolutionUnits->Append( _( "pixels/in" ) );
m_choiceResolutionUnits->Append( _( "pixels/mm" ) );
m_choiceResolutionUnits->SetSelection( 0 ); // pixels/in
resGrid->Add( m_choiceResolutionUnits, 0, wxEXPAND );
// Y Resolution row
resGrid->Add( new wxStaticText( this, wxID_ANY, _( "Y resolution:" ) ), 0, wxALIGN_CENTER_VERTICAL );
m_spinYResolution = new wxSpinCtrlDouble( this, wxID_ANY );
m_spinYResolution->SetRange( 1, 10000 );
m_spinYResolution->SetDigits( 3 );
resGrid->Add( m_spinYResolution, 1, wxEXPAND );
resGrid->AddSpacer( 0 ); // Empty space for units column
mainSizer->Add( resGrid, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 10 );
// Dialog buttons
wxStdDialogButtonSizer* btnSizer = CreateStdDialogButtonSizer( wxOK | wxCANCEL );
mainSizer->Add( btnSizer, 0, wxEXPAND | wxALL, 10 );
SetSizerAndFit( mainSizer );
Centre();
// Set initial values AFTER creating all controls
m_spinWidth->SetValue( m_width );
m_spinHeight->SetValue( m_height );
m_spinXResolution->SetValue( m_xResolution );
m_spinYResolution->SetValue( m_yResolution );
UpdatePixelSize();
// Bind events AFTER setting initial values
m_lockButton->Bind( wxEVT_BUTTON, &DIALOG_EXPORT_3D_IMAGE::OnLockToggle, this );
m_spinWidth->Bind( wxEVT_SPINCTRLDOUBLE, &DIALOG_EXPORT_3D_IMAGE::OnWidthChange, this );
m_spinHeight->Bind( wxEVT_SPINCTRLDOUBLE, &DIALOG_EXPORT_3D_IMAGE::OnHeightChange, this );
m_spinXResolution->Bind( wxEVT_SPINCTRLDOUBLE, &DIALOG_EXPORT_3D_IMAGE::OnXResolutionChange, this );
m_spinYResolution->Bind( wxEVT_SPINCTRLDOUBLE, &DIALOG_EXPORT_3D_IMAGE::OnYResolutionChange, this );
m_choiceSizeUnits->Bind( wxEVT_CHOICE, &DIALOG_EXPORT_3D_IMAGE::OnSizeUnitChange, this );
m_choiceResolutionUnits->Bind( wxEVT_CHOICE, &DIALOG_EXPORT_3D_IMAGE::OnResolutionUnitChange, this );
}
bool DIALOG_EXPORT_3D_IMAGE::TransferDataFromWindow()
{
// Convert current values back to pixels if needed
double width = m_spinWidth->GetValue();
double height = m_spinHeight->GetValue();
switch( m_sizeUnits )
{
case SIZE_UNITS::PIXELS:
m_width = static_cast<int>( width );
m_height = static_cast<int>( height );
break;
case SIZE_UNITS::PERCENT:
// Assume 100% = original size
m_width = static_cast<int>( width * m_originalSize.GetWidth() / 100.0 );
m_height = static_cast<int>( height * m_originalSize.GetHeight() / 100.0 );
break;
case SIZE_UNITS::MM:
// Convert mm to pixels using resolution
m_width = static_cast<int>( width * m_xResolution / 25.4 );
m_height = static_cast<int>( height * m_yResolution / 25.4 );
break;
case SIZE_UNITS::INCHES:
// Convert inches to pixels using resolution
m_width = static_cast<int>( width * m_xResolution );
m_height = static_cast<int>( height * m_yResolution );
break;
}
m_xResolution = m_spinXResolution->GetValue();
m_yResolution = m_spinYResolution->GetValue();
return true;
}
void DIALOG_EXPORT_3D_IMAGE::OnLockToggle( wxCommandEvent& aEvent )
{
m_lockAspectRatio = !m_lockAspectRatio;
if( m_lockAspectRatio )
{
m_lockButton->SetBitmap( KiBitmapBundle( BITMAPS::locked ) );
UpdateAspectRatio();
}
else
{
m_lockButton->SetBitmap( KiBitmapBundle( BITMAPS::unlocked ) );
}
}
void DIALOG_EXPORT_3D_IMAGE::OnWidthChange( wxSpinDoubleEvent& aEvent )
{
if( m_lockAspectRatio )
{
double width = m_spinWidth->GetValue();
double height;
if( m_sizeUnits == SIZE_UNITS::PERCENT )
height = m_originalSize.GetWidth() * width / 100.0 / m_aspectRatio;
else
height = width / m_aspectRatio;
// Ensure height is not less than 1
if( height < 1 )
height = 1;
m_spinHeight->SetValue( height );
}
UpdatePixelSize();
}
void DIALOG_EXPORT_3D_IMAGE::OnHeightChange( wxSpinDoubleEvent& aEvent )
{
if( m_lockAspectRatio )
{
double height = m_spinHeight->GetValue();
double width;
if( m_sizeUnits == SIZE_UNITS::PERCENT )
width = m_originalSize.GetHeight() * height / 100.0 * m_aspectRatio;
else
width = height * m_aspectRatio;
// Ensure width is not less than 1
if( width < 1 )
width = 1;
m_spinWidth->SetValue( width );
}
UpdatePixelSize();
}
void DIALOG_EXPORT_3D_IMAGE::OnXResolutionChange( wxSpinDoubleEvent& aEvent )
{
UpdatePixelSize();
}
void DIALOG_EXPORT_3D_IMAGE::OnYResolutionChange( wxSpinDoubleEvent& aEvent )
{
UpdatePixelSize();
}
void DIALOG_EXPORT_3D_IMAGE::OnSizeUnitChange( wxCommandEvent& aEvent )
{
SIZE_UNITS oldUnits = m_sizeUnits;
m_sizeUnits = static_cast<SIZE_UNITS>( m_choiceSizeUnits->GetSelection() );
ConvertSizeUnits( oldUnits, m_sizeUnits );
UpdatePixelSize();
}
void DIALOG_EXPORT_3D_IMAGE::OnResolutionUnitChange( wxCommandEvent& aEvent )
{
RESOLUTION_UNITS oldUnits = m_resolutionUnits;
m_resolutionUnits = static_cast<RESOLUTION_UNITS>( m_choiceResolutionUnits->GetSelection() );
ConvertResolutionUnits( oldUnits, m_resolutionUnits );
UpdatePixelSize();
}
wxSize DIALOG_EXPORT_3D_IMAGE::GetPixelSize( double aWidth, double aHeight, double aXResolution, double aYResolution, SIZE_UNITS aSizeUnits ) const
{
switch( aSizeUnits )
{
case SIZE_UNITS::PIXELS:
return wxSize( static_cast<int>( aWidth ), static_cast<int>( aHeight ) );
case SIZE_UNITS::PERCENT:
return wxSize( static_cast<int>( 100.0 * m_originalSize.GetWidth() ),
static_cast<int>( 100.0 * m_originalSize.GetHeight() ) );
case SIZE_UNITS::MM:
return wxSize( static_cast<int>( aWidth * aXResolution / 25.4 ),
static_cast<int>( aHeight * aYResolution / 25.4 ) );
case SIZE_UNITS::INCHES:
return wxSize( static_cast<int>( aWidth * aXResolution ),
static_cast<int>( aHeight * aYResolution ) );
default:
break;
}
return wxSize( 10, 10 ); // Should not happen
}
void DIALOG_EXPORT_3D_IMAGE::UpdatePixelSize()
{
double width = m_spinWidth->GetValue();
double height = m_spinHeight->GetValue();
double xRes = m_spinXResolution->GetValue();
double yRes = m_spinYResolution->GetValue();
// Convert resolution to standard pixels/inch
if( m_resolutionUnits == RESOLUTION_UNITS::PIXELS_PER_MM )
{
xRes *= 25.4; // Convert mm to inches
yRes *= 25.4;
}
wxSize pixelSize = GetPixelSize( width, height, xRes, yRes, m_sizeUnits );
m_pixelSizeLabel->SetLabel( wxString::Format( _( "%d × %d pixels" ), pixelSize.GetWidth(), pixelSize.GetHeight() ) );
}
void DIALOG_EXPORT_3D_IMAGE::UpdateAspectRatio()
{
m_aspectRatio = m_spinWidth->GetValue() / m_spinHeight->GetValue();
}
void DIALOG_EXPORT_3D_IMAGE::ConvertSizeUnits( SIZE_UNITS aFromUnit, SIZE_UNITS aToUnit )
{
if( aFromUnit == aToUnit )
return;
double width = m_spinWidth->GetValue();
double height = m_spinHeight->GetValue();
double xRes = m_spinXResolution->GetValue();
double yRes = m_spinYResolution->GetValue();
// Convert resolution to standard pixels/inch
if( m_resolutionUnits == RESOLUTION_UNITS::PIXELS_PER_MM )
{
xRes *= 25.4; // Convert mm to inches
yRes *= 25.4;
}
// Convert to pixels first
wxSize pixelSize = GetPixelSize( width, height, xRes, yRes, aFromUnit );
// Convert from pixels to target unit
switch( aToUnit )
{
case SIZE_UNITS::PIXELS:
m_spinWidth->SetValue( pixelSize.GetWidth() );
m_spinHeight->SetValue( pixelSize.GetHeight() );
break;
case SIZE_UNITS::PERCENT:
m_spinWidth->SetValue( pixelSize.GetWidth() * 100.0 / m_originalSize.GetWidth() );
m_spinHeight->SetValue( pixelSize.GetHeight() * 100.0 / m_originalSize.GetHeight() );
break;
case SIZE_UNITS::MM:
m_spinWidth->SetValue( pixelSize.GetWidth() * 25.4 / xRes );
m_spinHeight->SetValue( pixelSize.GetHeight() * 25.4 / yRes );
break;
case SIZE_UNITS::INCHES:
m_spinWidth->SetValue( pixelSize.GetWidth() / xRes );
m_spinHeight->SetValue( pixelSize.GetHeight() / yRes );
break;
}
}
void DIALOG_EXPORT_3D_IMAGE::ConvertResolutionUnits( RESOLUTION_UNITS aFromUnit, RESOLUTION_UNITS aToUnit )
{
if( aFromUnit == aToUnit )
return;
double xRes = m_spinXResolution->GetValue();
double yRes = m_spinYResolution->GetValue();
if( aFromUnit == RESOLUTION_UNITS::PIXELS_PER_INCH && aToUnit == RESOLUTION_UNITS::PIXELS_PER_MM )
{
// Convert from pixels/inch to pixels/mm
m_spinXResolution->SetValue( xRes / 25.4 );
m_spinYResolution->SetValue( yRes / 25.4 );
}
else if( aFromUnit == RESOLUTION_UNITS::PIXELS_PER_MM && aToUnit == RESOLUTION_UNITS::PIXELS_PER_INCH )
{
// Convert from pixels/mm to pixels/inch
m_spinXResolution->SetValue( xRes * 25.4 );
m_spinYResolution->SetValue( yRes * 25.4 );
}
}

94
3d-viewer/dialogs/dialog_export_3d_image.h

@ -0,0 +1,94 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/gpl-3.0.html
* or you may search the http://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include "dialog_shim.h"
#include <3d_viewer/eda_3d_viewer_frame.h> // for EDA_3D_VIEWER_EXPORT_FORMAT
#include <wx/spinctrl.h>
#include <wx/choice.h>
#include <wx/stattext.h>
#include <wx/bmpbuttn.h>
enum class SIZE_UNITS
{
PIXELS,
PERCENT,
MM,
INCHES
};
enum class RESOLUTION_UNITS
{
PIXELS_PER_INCH,
PIXELS_PER_MM
};
class DIALOG_EXPORT_3D_IMAGE : public DIALOG_SHIM
{
public:
DIALOG_EXPORT_3D_IMAGE( wxWindow* aParent,
const wxSize& aSize );
wxSize GetSize() const { return wxSize( m_width, m_height ); }
double GetXResolution() const { return m_xResolution; }
double GetYResolution() const { return m_yResolution; }
private:
bool TransferDataFromWindow() override;
void OnLockToggle( wxCommandEvent& aEvent );
void OnWidthChange( wxSpinDoubleEvent& aEvent );
void OnHeightChange( wxSpinDoubleEvent& aEvent );
void OnXResolutionChange( wxSpinDoubleEvent& aEvent );
void OnYResolutionChange( wxSpinDoubleEvent& aEvent );
void OnSizeUnitChange( wxCommandEvent& aEvent );
void OnResolutionUnitChange( wxCommandEvent& aEvent );
wxSize GetPixelSize( double aWidth, double aHeight, double aXResolution, double aYResolution, SIZE_UNITS aSizeUnits ) const;
void UpdatePixelSize();
void UpdateAspectRatio();
void ConvertSizeUnits( SIZE_UNITS aFromUnit, SIZE_UNITS aToUnit );
void ConvertResolutionUnits( RESOLUTION_UNITS aFromUnit, RESOLUTION_UNITS aToUnit );
EDA_3D_VIEWER_EXPORT_FORMAT m_format;
wxSize m_originalSize;
int m_width;
int m_height;
double m_xResolution;
double m_yResolution;
double m_aspectRatio;
bool m_lockAspectRatio;
SIZE_UNITS m_sizeUnits;
RESOLUTION_UNITS m_resolutionUnits;
wxSpinCtrlDouble* m_spinWidth;
wxSpinCtrlDouble* m_spinHeight;
wxSpinCtrlDouble* m_spinXResolution;
wxSpinCtrlDouble* m_spinYResolution;
wxChoice* m_choiceSizeUnits;
wxChoice* m_choiceResolutionUnits;
wxBitmapButton* m_lockButton;
wxStaticText* m_pixelSizeLabel;
};
Loading…
Cancel
Save