/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016 Mario Luzeiro * Copyright (C) 2015 Cirilo Bernardo * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2015-2018 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 2 * 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/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /** * @file panel_prev_model.cpp */ #include <3d_canvas/eda_3d_canvas.h> #include #include #include #include #include #include #include "project.h" #include "panel_prev_model.h" #include PANEL_PREV_3D::PANEL_PREV_3D( wxWindow* aParent, EDA_UNITS_T aUnits, S3D_CACHE* aCacheManager, MODULE* aModuleCopy, COLORS_DESIGN_SETTINGS *aColors, std::vector *aParentInfoList ) : PANEL_PREV_3D_BASE( aParent, wxID_ANY ) { m_userUnits = aUnits; initPanel(); // Initialize the color settings to draw the board and the footprint if( aColors ) m_dummyBoard->SetColorsSettings( aColors ); else { static COLORS_DESIGN_SETTINGS defaultColors( FRAME_PCB_DISPLAY3D ); m_dummyBoard->SetColorsSettings( &defaultColors ); } if( NULL != aCacheManager ) m_resolver = aCacheManager->GetResolver(); m_parentInfoList = aParentInfoList; m_dummyBoard->Add( (MODULE*)aModuleCopy ); m_copyModule = aModuleCopy; // Set 3d viewer configuration for preview m_settings3Dviewer = new CINFO3D_VISU(); // Create the 3D canvas m_previewPane = new EDA_3D_CANVAS( this, COGL_ATT_LIST::GetAttributesList( true ), m_dummyBoard, *m_settings3Dviewer, aCacheManager ); m_SizerPanelView->Add( m_previewPane, 1, wxEXPAND ); m_previewPane->Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( PANEL_PREV_3D::onEnterPreviewCanvas ), NULL, this ); } PANEL_PREV_3D::~PANEL_PREV_3D() { m_previewPane->Disconnect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( PANEL_PREV_3D::onEnterPreviewCanvas ), NULL, this ); delete m_settings3Dviewer; delete m_dummyBoard; delete m_previewPane; } void PANEL_PREV_3D::initPanel() { m_resolver = NULL; currentModelFile.clear(); m_dummyBoard = new BOARD(); m_currentSelectedIdx = -1; // Set the bitmap of 3D view buttons: m_bpvTop->SetBitmap( KiBitmap( axis3d_top_xpm ) ); m_bpvFront->SetBitmap( KiBitmap( axis3d_front_xpm ) ); m_bpvBack->SetBitmap( KiBitmap( axis3d_back_xpm ) ); m_bpvLeft->SetBitmap( KiBitmap( axis3d_left_xpm ) ); m_bpvRight->SetBitmap( KiBitmap( axis3d_right_xpm ) ); m_bpvBottom->SetBitmap( KiBitmap( axis3d_bottom_xpm ) ); m_bpvISO->SetBitmap( KiBitmap( ortho_xpm ) ); m_bpUpdate->SetBitmap( KiBitmap( reload_xpm ) ); // Set the min and max values of spin buttons (mandatory on Linux) // They are not used, so they are set to min and max 32 bits int values // (the min and max values supported by a wxSpinButton) // It avoids blocking the up or down arrows when reaching this limit after // a few clicks. wxSpinButton* spinButtonList[] = { m_spinXscale, m_spinYscale, m_spinZscale, m_spinXrot, m_spinYrot, m_spinZrot, m_spinXoffset,m_spinYoffset, m_spinZoffset }; for( int ii = 0; ii < 9; ii++ ) { spinButtonList[ii]->SetRange( INT_MIN, INT_MAX ); } wxString units; switch( m_userUnits ) { case INCHES: units = _( "inches" ); break; case MILLIMETRES: units = _( "mm" ); break; default: break; } if( !units.IsEmpty() ) { units = wxString::Format( _( "Offset (%s)" ), units ); m_staticTextOffset->SetLabel( units ); } } /** * @brief checkRotation * Ensure -MAX_ROTATION <= rotation <= MAX_ROTATION * aRotation will be normalized between -MAX_ROTATION and MAX_ROTATION * @param aRotation: in out parameter */ static void checkRotation( double& aRotation ) { if( aRotation > MAX_ROTATION ) { int n = aRotation / MAX_ROTATION; aRotation -= MAX_ROTATION * n; } else if( aRotation < -MAX_ROTATION ) { int n = -aRotation / MAX_ROTATION; aRotation += MAX_ROTATION * n; } } static bool validateFloatTextCtrl( wxTextCtrl* aTextCtrl ) { if( aTextCtrl == NULL ) return false; if( aTextCtrl->GetLineLength(0) == 0 ) // This will skip the got and event with empty field return false; if( aTextCtrl->GetLineLength(0) == 1 ) { if( (aTextCtrl->GetLineText(0).compare( "." ) == 0) || (aTextCtrl->GetLineText(0).compare( "," ) == 0) ) return false; } return true; } static void incrementTextCtrl( wxTextCtrl* aTextCtrl, double aInc, double aMinval, double aMaxval ) { if( !validateFloatTextCtrl( aTextCtrl ) ) return; double curr_value = 0; aTextCtrl->GetValue().ToDouble( &curr_value ); curr_value += aInc; if( curr_value > aMaxval ) curr_value = aMaxval; if( curr_value < aMinval ) curr_value = aMinval; aTextCtrl->SetValue( wxString::Format( "%.4f", curr_value ) ); } void PANEL_PREV_3D::SetModelDataIdx( int idx, bool aReloadPreviewModule ) { wxASSERT( m_parentInfoList != NULL ); if( m_parentInfoList && (idx >= 0) ) { wxASSERT( (unsigned int)idx < (*m_parentInfoList).size() ); if( (unsigned int)idx < (*m_parentInfoList).size() ) { m_currentSelectedIdx = -1; // In case that we receive events on the // next updates, it will set first an // invalid selection const MODULE_3D_SETTINGS *aModel = (const MODULE_3D_SETTINGS *)&((*m_parentInfoList)[idx]); xscale->SetValue( wxString::Format( "%.4f", aModel->m_Scale.x ) ); yscale->SetValue( wxString::Format( "%.4f", aModel->m_Scale.y ) ); zscale->SetValue( wxString::Format( "%.4f", aModel->m_Scale.z ) ); xrot->SetValue( wxString::Format( "%.2f", aModel->m_Rotation.x ) ); yrot->SetValue( wxString::Format( "%.2f", aModel->m_Rotation.y ) ); zrot->SetValue( wxString::Format( "%.2f", aModel->m_Rotation.z ) ); // Convert from internal units (mm) to user units double scaler = 1; switch( m_userUnits ) { case MILLIMETRES: scaler = 1.0f; break; case INCHES: scaler = 25.4f; break; default: wxASSERT( 0 ); } xoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.x / scaler ) ); yoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.y / scaler ) ); zoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.z / scaler ) ); UpdateModelName( aModel->m_Filename ); if( aReloadPreviewModule && m_previewPane ) { updateListOnModelCopy(); m_previewPane->ReloadRequest(); m_previewPane->Request_refresh(); } m_currentSelectedIdx = idx; } } if( m_previewPane ) { m_previewPane->SetFocus(); } return; } void PANEL_PREV_3D::ResetModelData( bool aReloadPreviewModule ) { m_currentSelectedIdx = -1; xscale->SetValue( wxString::FromDouble( 1.0 ) ); yscale->SetValue( wxString::FromDouble( 1.0 ) ); zscale->SetValue( wxString::FromDouble( 1.0 ) ); xrot->SetValue( wxString::FromDouble( 0.0 ) ); yrot->SetValue( wxString::FromDouble( 0.0 ) ); zrot->SetValue( wxString::FromDouble( 0.0 ) ); xoff->SetValue( wxString::FromDouble( 0.0 ) ); yoff->SetValue( wxString::FromDouble( 0.0 ) ); zoff->SetValue( wxString::FromDouble( 0.0 ) ); // This will update the model on the preview board with the current list of 3d shapes if( aReloadPreviewModule ) { updateListOnModelCopy(); if( m_previewPane ) { m_previewPane->ReloadRequest(); m_previewPane->Request_refresh(); } } if( m_previewPane ) m_previewPane->SetFocus(); } void PANEL_PREV_3D::UpdateModelName( wxString const& aModelName ) { bool newModel = false; m_modelInfo.m_Filename = aModelName; // if the model name is a directory simply clear the current model if( aModelName.empty() || wxFileName::DirExists( aModelName ) ) { currentModelFile.clear(); m_modelInfo.m_Filename.clear(); } else { wxString newModelFile; if( m_resolver ) newModelFile = m_resolver->ResolvePath( aModelName ); if( !newModelFile.empty() && newModelFile.Cmp( currentModelFile ) ) newModel = true; currentModelFile = newModelFile; } if( currentModelFile.empty() || newModel ) { updateListOnModelCopy(); if( m_previewPane ) { m_previewPane->ReloadRequest(); m_previewPane->Refresh(); } if( currentModelFile.empty() ) return; } else { if( m_previewPane ) m_previewPane->Refresh(); } if( m_previewPane ) m_previewPane->SetFocus(); return; } void PANEL_PREV_3D::updateOrientation( wxCommandEvent &event ) { wxTextCtrl *textCtrl = (wxTextCtrl *)event.GetEventObject(); if( textCtrl == NULL ) return; if( textCtrl->GetLineLength(0) == 0 ) // This will skip the got and event with empty field return; if( textCtrl->GetLineLength(0) == 1 ) if( (textCtrl->GetLineText(0).compare( "." ) == 0) || (textCtrl->GetLineText(0).compare( "," ) == 0) ) return; SGPOINT scale; SGPOINT rotation; SGPOINT offset; getOrientationVars( scale, rotation, offset ); m_modelInfo.m_Scale.x = scale.x; m_modelInfo.m_Scale.y = scale.y; m_modelInfo.m_Scale.z = scale.z; m_modelInfo.m_Offset.x = offset.x; m_modelInfo.m_Offset.y = offset.y; m_modelInfo.m_Offset.z = offset.z; m_modelInfo.m_Rotation.x = rotation.x; m_modelInfo.m_Rotation.y = rotation.y; m_modelInfo.m_Rotation.z = rotation.z; if( m_currentSelectedIdx >= 0 ) { // This will update the parent list with the new data (*m_parentInfoList)[m_currentSelectedIdx] = m_modelInfo; // It will update the copy model in the preview board updateListOnModelCopy(); // Since the OpenGL render does not need to be reloaded to update the // shapes position, we just request to redraw again the canvas if( m_previewPane ) m_previewPane->Refresh(); } } void PANEL_PREV_3D::onIncrementRot( wxSpinEvent& event ) { wxSpinButton* spinCtrl = (wxSpinButton*) event.GetEventObject(); wxTextCtrl * textCtrl = xrot; if( spinCtrl == m_spinYrot ) textCtrl = yrot; else if( spinCtrl == m_spinZrot ) textCtrl = zrot; incrementTextCtrl( textCtrl, ROTATION_INCREMENT, -MAX_ROTATION, MAX_ROTATION ); } void PANEL_PREV_3D::onDecrementRot( wxSpinEvent& event ) { wxSpinButton* spinCtrl = (wxSpinButton*) event.GetEventObject(); wxTextCtrl * textCtrl = xrot; if( spinCtrl == m_spinYrot ) textCtrl = yrot; else if( spinCtrl == m_spinZrot ) textCtrl = zrot; incrementTextCtrl( textCtrl, -ROTATION_INCREMENT, -MAX_ROTATION, MAX_ROTATION ); } void PANEL_PREV_3D::onIncrementScale( wxSpinEvent& event ) { wxSpinButton* spinCtrl = (wxSpinButton*) event.GetEventObject(); wxTextCtrl * textCtrl = xscale; if( spinCtrl == m_spinYscale ) textCtrl = yscale; else if( spinCtrl == m_spinZscale ) textCtrl = zscale; incrementTextCtrl( textCtrl, SCALE_INCREMENT, 1/MAX_SCALE, MAX_SCALE ); } void PANEL_PREV_3D::onDecrementScale( wxSpinEvent& event ) { wxSpinButton* spinCtrl = (wxSpinButton*) event.GetEventObject(); wxTextCtrl * textCtrl = xscale; if( spinCtrl == m_spinYscale ) textCtrl = yscale; else if( spinCtrl == m_spinZscale ) textCtrl = zscale; incrementTextCtrl( textCtrl, -SCALE_INCREMENT, 1/MAX_SCALE, MAX_SCALE ); } void PANEL_PREV_3D::onIncrementOffset( wxSpinEvent& event ) { wxSpinButton* spinCtrl = (wxSpinButton*) event.GetEventObject(); wxTextCtrl * textCtrl = xoff; if( spinCtrl == m_spinYoffset ) textCtrl = yoff; else if( spinCtrl == m_spinZoffset ) textCtrl = zoff; double step = OFFSET_INCREMENT_MM; if( m_userUnits == INCHES ) step = OFFSET_INCREMENT_MIL/1000.0; incrementTextCtrl( textCtrl, step, -MAX_OFFSET, MAX_OFFSET ); } void PANEL_PREV_3D::onDecrementOffset( wxSpinEvent& event ) { wxSpinButton* spinCtrl = (wxSpinButton*) event.GetEventObject(); wxTextCtrl * textCtrl = xoff; if( spinCtrl == m_spinYoffset ) textCtrl = yoff; else if( spinCtrl == m_spinZoffset ) textCtrl = zoff; double step = OFFSET_INCREMENT_MM; if( m_userUnits == INCHES ) step = OFFSET_INCREMENT_MIL/1000.0; incrementTextCtrl( textCtrl, -step, -MAX_OFFSET, MAX_OFFSET ); } void PANEL_PREV_3D::onMouseWheelScale( wxMouseEvent& event ) { wxTextCtrl* textCtrl = (wxTextCtrl*) event.GetEventObject(); double step = SCALE_INCREMENT; if( event.ShiftDown( ) ) step = SCALE_INCREMENT_FINE; if( event.GetWheelRotation() >= 0 ) step = -step; incrementTextCtrl( textCtrl, step, 1/MAX_SCALE, MAX_SCALE ); } void PANEL_PREV_3D::onMouseWheelRot( wxMouseEvent& event ) { wxTextCtrl* textCtrl = (wxTextCtrl*) event.GetEventObject(); wxKeyboardState kbdState; double step = ROTATION_INCREMENT_WHEEL; if( event.ShiftDown( ) ) step = ROTATION_INCREMENT_WHEEL_FINE; if( event.GetWheelRotation() >= 0 ) step = -step; incrementTextCtrl( textCtrl, step, -MAX_ROTATION, MAX_ROTATION ); } void PANEL_PREV_3D::onMouseWheelOffset( wxMouseEvent& event ) { wxTextCtrl* textCtrl = (wxTextCtrl*) event.GetEventObject(); double step = OFFSET_INCREMENT_MM; if( event.ShiftDown( ) ) step = OFFSET_INCREMENT_MM_FINE; if( m_userUnits == INCHES ) { step = OFFSET_INCREMENT_MIL/1000.0; if( event.ShiftDown( ) ) step = OFFSET_INCREMENT_MIL_FINE/1000.0; } if( event.GetWheelRotation() >= 0 ) step = -step; incrementTextCtrl( textCtrl, step, -MAX_OFFSET, MAX_OFFSET ); } void PANEL_PREV_3D::getOrientationVars( SGPOINT& aScale, SGPOINT& aRotation, SGPOINT& aOffset ) { if( NULL == xscale || NULL == yscale || NULL == zscale || NULL == xrot || NULL == yrot || NULL == zrot || NULL == xoff || NULL == yoff || NULL == zoff ) { return; } xscale->GetValue().ToDouble( &aScale.x ); yscale->GetValue().ToDouble( &aScale.y ); zscale->GetValue().ToDouble( &aScale.z ); xrot->GetValue().ToDouble( &aRotation.x ); yrot->GetValue().ToDouble( &aRotation.y ); zrot->GetValue().ToDouble( &aRotation.z ); checkRotation( aRotation.x ); checkRotation( aRotation.y ); checkRotation( aRotation.z ); xoff->GetValue().ToDouble( &aOffset.x ); yoff->GetValue().ToDouble( &aOffset.y ); zoff->GetValue().ToDouble( &aOffset.z ); // Convert from user units to internal units (mm) double scaler = 1.0f; switch( m_userUnits ) { case MILLIMETRES: scaler = 1.0f; break; case INCHES: scaler = 25.4f; break; default: wxASSERT( 0 ); } aOffset.x *= scaler; aOffset.y *= scaler; aOffset.z *= scaler; return; } bool PANEL_PREV_3D::ValidateWithMessage( wxString& aErrorMessage ) { bool invalidScale = false; for( unsigned int idx = 0; idx < m_parentInfoList->size(); ++idx ) { wxString msg; bool addError = false; MODULE_3D_SETTINGS& s3dshape = (*m_parentInfoList)[idx]; SGPOINT scale; scale.x = s3dshape.m_Scale.x; scale.y = s3dshape.m_Scale.y; scale.z = s3dshape.m_Scale.z; if( 1/MAX_SCALE > scale.x || MAX_SCALE < scale.x ) { invalidScale = true; addError = true; msg += _( "Invalid X scale" ); } if( 1/MAX_SCALE > scale.y || MAX_SCALE < scale.y ) { invalidScale = true; addError = true; if( !msg.IsEmpty() ) msg += "\n"; msg += _( "Invalid Y scale" ); } if( 1/MAX_SCALE > scale.z || MAX_SCALE < scale.z ) { invalidScale = true; addError = true; if( !msg.IsEmpty() ) msg += "\n"; msg += _( "Invalid Z scale" ); } if( addError ) { msg.Prepend( s3dshape.m_Filename + "\n" ); if( !aErrorMessage.IsEmpty() ) aErrorMessage += "\n\n"; aErrorMessage += msg; } } if( !aErrorMessage.IsEmpty() ) { aErrorMessage += "\n\n"; aErrorMessage += wxString::Format( "Min value = %.4f and max value = %.4f", 1/MAX_SCALE, MAX_SCALE ); } return invalidScale == false; } void PANEL_PREV_3D::updateListOnModelCopy() { auto draw3D = &m_copyModule->Models(); draw3D->clear(); draw3D->insert( draw3D->end(), m_parentInfoList->begin(), m_parentInfoList->end() ); }