From e4b8fde25bea26c5347890ca7010a0a0bb7776cf Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Mon, 16 Mar 2026 19:05:35 +0000 Subject: [PATCH] PANEL_PREVIEW_3D_MODEL does its own pretty-printing. Fixes https://gitlab.com/kicad/code/kicad/-/issues/23401 --- 3d-viewer/dialogs/panel_preview_3d_model.cpp | 227 +++++++++---------- 3d-viewer/dialogs/panel_preview_3d_model.h | 2 + common/widgets/text_ctrl_eval.cpp | 18 +- include/widgets/text_ctrl_eval.h | 8 + 4 files changed, 132 insertions(+), 123 deletions(-) diff --git a/3d-viewer/dialogs/panel_preview_3d_model.cpp b/3d-viewer/dialogs/panel_preview_3d_model.cpp index b924949076..8421c89371 100644 --- a/3d-viewer/dialogs/panel_preview_3d_model.cpp +++ b/3d-viewer/dialogs/panel_preview_3d_model.cpp @@ -56,8 +56,51 @@ #include <3d_navlib/nl_footprint_properties_plugin.h> #endif -PANEL_PREVIEW_3D_MODEL::PANEL_PREVIEW_3D_MODEL( wxWindow* aParent, PCB_BASE_FRAME* aFrame, - FOOTPRINT* aFootprint, +static wxString evaluateTextCtrl( const wxString& aValue ) +{ + // NUMERIC_EVALUATOR doesn't handle UTF-8 multi-byte characters properly, + // so skip evaluation if the string contains non-ASCII characters (e.g., degree symbols) + for( wxUniChar c : aValue ) + { + if( !c.IsAscii() ) + return aValue; + } + + // Attempt to evaluate formula; if successful return result, otherwise return original + NUMERIC_EVALUATOR eval( EDA_UNITS::UNSCALED ); + + if( eval.Process( aValue ) ) + return eval.Result(); + + return aValue; +} + + +/** + * Ensure -MAX_ROTATION <= rotation <= MAX_ROTATION. + * + * @param \a aRotation will be normalized between -MAX_ROTATION and MAX_ROTATION. + */ +static double rotationFromString( const wxString& aValue ) +{ + double rotation = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::DEGREES, aValue ); + + if( rotation > MAX_ROTATION ) + { + int n = KiROUND( rotation / MAX_ROTATION ); + rotation -= MAX_ROTATION * n; + } + else if( rotation < -MAX_ROTATION ) + { + int n = KiROUND( -rotation / MAX_ROTATION ); + rotation += MAX_ROTATION * n; + } + + return rotation; +} + + +PANEL_PREVIEW_3D_MODEL::PANEL_PREVIEW_3D_MODEL( wxWindow* aParent, PCB_BASE_FRAME* aFrame, FOOTPRINT* aFootprint, std::vector* aParentModelList ) : PANEL_PREVIEW_3D_MODEL_BASE( aParent, PANEL_PREVIEW_3D_MODEL_ID ), m_parentFrame( aFrame ), @@ -116,6 +159,27 @@ PANEL_PREVIEW_3D_MODEL::PANEL_PREVIEW_3D_MODEL( wxWindow* aParent, PCB_BASE_FRAM for( wxSpinButton* button : spinButtonList ) button->SetRange(INT_MIN, INT_MAX ); + for( TEXT_CTRL_EVAL* rotCtrl : { xrot, yrot, zrot } ) + { + rotCtrl->SetCustomEval( + [&]( TEXT_CTRL_EVAL* aCtrl ) + { + double value = rotationFromString( evaluateTextCtrl( aCtrl->GetValue() ) ); + aCtrl->SetValue( formatRotationValue( value ) ); + } ); + } + + for( TEXT_CTRL_EVAL* scaleCtrl : { xscale, yscale, zscale } ) + { + scaleCtrl->SetCustomEval( + [&]( TEXT_CTRL_EVAL* aCtrl ) + { + double value = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::UNSCALED, + evaluateTextCtrl( aCtrl->GetValue() ) ); + aCtrl->SetValue( formatScaleValue( value ) ); + } ); + } + m_parentModelList = aParentModelList; m_dummyFootprint = new FOOTPRINT( *aFootprint ); @@ -128,12 +192,10 @@ PANEL_PREVIEW_3D_MODEL::PANEL_PREVIEW_3D_MODEL( wxWindow* aParent, PCB_BASE_FRAM m_dummyFootprint->SetOrientation( ANGLE_0 ); - m_dummyBoard->Add( m_dummyFootprint ); // Create the 3D canvas - m_previewPane = new EDA_3D_CANVAS( this, - OGL_ATT_LIST::GetAttributesList( ANTIALIASING_MODE::AA_8X ), + m_previewPane = new EDA_3D_CANVAS( this, OGL_ATT_LIST::GetAttributesList( ANTIALIASING_MODE::AA_8X ), m_boardAdapter, m_currentCamera, PROJECT_PCB::Get3DCacheManager( &aFrame->Prj() ) ); @@ -181,13 +243,9 @@ PANEL_PREVIEW_3D_MODEL::PANEL_PREVIEW_3D_MODEL( wxWindow* aParent, PCB_BASE_FRAM m_SizerPanelView->Add( m_previewPane, 1, wxEXPAND, 5 ); for( wxEventType eventType : { wxEVT_MENU_OPEN, wxEVT_MENU_CLOSE, wxEVT_MENU_HIGHLIGHT } ) - { - Connect( eventType, wxMenuEventHandler( PANEL_PREVIEW_3D_MODEL::OnMenuEvent ), nullptr, - this ); - } + Connect( eventType, wxMenuEventHandler( PANEL_PREVIEW_3D_MODEL::OnMenuEvent ), nullptr, this ); - aFrame->Connect( EDA_EVT_UNITS_CHANGED, - wxCommandEventHandler( PANEL_PREVIEW_3D_MODEL::onUnitsChanged ), + aFrame->Connect( EDA_EVT_UNITS_CHANGED, wxCommandEventHandler( PANEL_PREVIEW_3D_MODEL::onUnitsChanged ), nullptr, this ); Bind( wxCUSTOM_PANEL_SHOWN_EVENT, &PANEL_PREVIEW_3D_MODEL::onPanelShownEvent, this ); @@ -241,31 +299,6 @@ void PANEL_PREVIEW_3D_MODEL::loadSettings() } -/** - * Ensure -MAX_ROTATION <= rotation <= MAX_ROTATION. - * - * @param \a aRotation will be normalized between -MAX_ROTATION and MAX_ROTATION. - */ -static double rotationFromString( const wxString& aValue ) -{ - double rotation = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::DEGREES, - aValue ); - - if( rotation > MAX_ROTATION ) - { - int n = KiROUND( rotation / MAX_ROTATION ); - rotation -= MAX_ROTATION * n; - } - else if( rotation < -MAX_ROTATION ) - { - int n = KiROUND( -rotation / MAX_ROTATION ); - rotation += MAX_ROTATION * n; - } - - return rotation; -} - - wxString PANEL_PREVIEW_3D_MODEL::formatScaleValue( double aValue ) { return wxString::Format( wxT( "%.4f" ), @@ -345,26 +378,6 @@ void PANEL_PREVIEW_3D_MODEL::SetSelectedModel( int idx ) } -static wxString evaluateTextCtrlFormula( const wxString& aValue ) -{ - // NUMERIC_EVALUATOR doesn't handle UTF-8 multi-byte characters properly, - // so skip evaluation if the string contains non-ASCII characters (e.g., degree symbols) - for( wxUniChar c : aValue ) - { - if( !c.IsAscii() ) - return aValue; - } - - // Attempt to evaluate formula; if successful return result, otherwise return original - NUMERIC_EVALUATOR eval( EDA_UNITS::UNSCALED ); - - if( eval.Process( aValue ) ) - return eval.Result(); - - return aValue; -} - - void PANEL_PREVIEW_3D_MODEL::updateOrientation( wxCommandEvent &event ) { if( m_parentModelList && m_selected >= 0 && m_selected < (int) m_parentModelList->size() ) @@ -372,27 +385,27 @@ void PANEL_PREVIEW_3D_MODEL::updateOrientation( wxCommandEvent &event ) // Write settings back to the parent FP_3DMODEL* modelInfo = &m_parentModelList->at( (unsigned) m_selected ); - modelInfo->m_Scale.x = EDA_UNIT_UTILS::UI::DoubleValueFromString( - pcbIUScale, EDA_UNITS::UNSCALED, evaluateTextCtrlFormula( xscale->GetValue() ) ); - modelInfo->m_Scale.y = EDA_UNIT_UTILS::UI::DoubleValueFromString( - pcbIUScale, EDA_UNITS::UNSCALED, evaluateTextCtrlFormula( yscale->GetValue() ) ); - modelInfo->m_Scale.z = EDA_UNIT_UTILS::UI::DoubleValueFromString( - pcbIUScale, EDA_UNITS::UNSCALED, evaluateTextCtrlFormula( zscale->GetValue() ) ); + modelInfo->m_Scale.x = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::UNSCALED, + evaluateTextCtrl( xscale->GetValue() ) ); + modelInfo->m_Scale.y = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::UNSCALED, + evaluateTextCtrl( yscale->GetValue() ) ); + modelInfo->m_Scale.z = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::UNSCALED, + evaluateTextCtrl( zscale->GetValue() ) ); // Rotation is stored in the file as positive-is-CW, but we use positive-is-CCW in the GUI // to match the rest of KiCad - modelInfo->m_Rotation.x = -rotationFromString( evaluateTextCtrlFormula( xrot->GetValue() ) ); - modelInfo->m_Rotation.y = -rotationFromString( evaluateTextCtrlFormula( yrot->GetValue() ) ); - modelInfo->m_Rotation.z = -rotationFromString( evaluateTextCtrlFormula( zrot->GetValue() ) ); + modelInfo->m_Rotation.x = -rotationFromString( evaluateTextCtrl( xrot->GetValue() ) ); + modelInfo->m_Rotation.y = -rotationFromString( evaluateTextCtrl( yrot->GetValue() ) ); + modelInfo->m_Rotation.z = -rotationFromString( evaluateTextCtrl( zrot->GetValue() ) ); modelInfo->m_Offset.x = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, - evaluateTextCtrlFormula( xoff->GetValue() ) ) + evaluateTextCtrl( xoff->GetValue() ) ) / pcbIUScale.IU_PER_MM; modelInfo->m_Offset.y = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, - evaluateTextCtrlFormula( yoff->GetValue() ) ) + evaluateTextCtrl( yoff->GetValue() ) ) / pcbIUScale.IU_PER_MM; modelInfo->m_Offset.z = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, - evaluateTextCtrlFormula( zoff->GetValue() ) ) + evaluateTextCtrl( zoff->GetValue() ) ) / pcbIUScale.IU_PER_MM; // Update the dummy footprint for the preview @@ -439,8 +452,7 @@ void PANEL_PREVIEW_3D_MODEL::View3DSettings( wxCommandEvent& event ) BOARD_DESIGN_SETTINGS bds = m_dummyBoard->GetDesignSettings(); int thickness = bds.GetBoardThickness(); - WX_UNIT_ENTRY_DIALOG dlg( m_parentFrame, _( "3D Preview Options" ), _( "Board thickness:" ), - thickness ); + WX_UNIT_ENTRY_DIALOG dlg( m_parentFrame, _( "3D Preview Options" ), _( "Board thickness:" ), thickness ); if( dlg.ShowModal() != wxID_OK ) return; @@ -476,14 +488,13 @@ void PANEL_PREVIEW_3D_MODEL::doIncrementScale( wxSpinEvent& event, double aSign if( wxGetMouseState().ShiftDown( ) ) step = SCALE_INCREMENT_FINE; - double curr_value = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::UNSCALED, - textCtrl->GetValue() ); + double value = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::UNSCALED, textCtrl->GetValue() ); - curr_value += ( step * aSign ); - curr_value = std::max( 1/MAX_SCALE, curr_value ); - curr_value = std::min( curr_value, MAX_SCALE ); + value += ( step * aSign ); + value = std::max( 1/MAX_SCALE, value ); + value = std::min( value, MAX_SCALE ); - textCtrl->SetValue( formatScaleValue( curr_value ) ); + textCtrl->SetValue( formatScaleValue( value ) ); } @@ -505,14 +516,11 @@ void PANEL_PREVIEW_3D_MODEL::doIncrementRotation( wxSpinEvent& aEvent, double aS if( wxGetMouseState().ShiftDown( ) ) step = ROTATION_INCREMENT_FINE; - double curr_value = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::DEGREES, - textCtrl->GetValue() ); + double value = rotationFromString( textCtrl->GetValue() ); - curr_value += ( step * aSign ); - curr_value = std::max( -MAX_ROTATION, curr_value ); - curr_value = std::min( curr_value, MAX_ROTATION ); + value += ( step * aSign ); - textCtrl->SetValue( formatRotationValue( curr_value ) ); + textCtrl->SetValue( formatRotationValue( value ) ); } @@ -542,15 +550,14 @@ void PANEL_PREVIEW_3D_MODEL::doIncrementOffset( wxSpinEvent& event, double aSign step_mm = 25.4*OFFSET_INCREMENT_MIL_FINE/1000;; } - double curr_value_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, - textCtrl->GetValue() ) - / pcbIUScale.IU_PER_MM; + double value_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, textCtrl->GetValue() ) + / pcbIUScale.IU_PER_MM; - curr_value_mm += ( step_mm * aSign ); - curr_value_mm = std::max( -MAX_OFFSET, curr_value_mm ); - curr_value_mm = std::min( curr_value_mm, MAX_OFFSET ); + value_mm += ( step_mm * aSign ); + value_mm = std::max( -MAX_OFFSET, value_mm ); + value_mm = std::min( value_mm, MAX_OFFSET ); - textCtrl->SetValue( formatOffsetValue( curr_value_mm ) ); + textCtrl->SetValue( formatOffsetValue( value_mm ) ); } @@ -568,14 +575,13 @@ void PANEL_PREVIEW_3D_MODEL::onMouseWheelScale( wxMouseEvent& event ) if( event.GetWheelRotation() >= 0 ) step = -step; - double curr_value = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::UNSCALED, - textCtrl->GetValue() ); + double value = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::UNSCALED, textCtrl->GetValue() ); - curr_value += step; - curr_value = std::max( 1/MAX_SCALE, curr_value ); - curr_value = std::min( curr_value, MAX_SCALE ); + value += step; + value = std::max( 1/MAX_SCALE, value ); + value = std::min( value, MAX_SCALE ); - textCtrl->SetValue( formatScaleValue( curr_value ) ); + textCtrl->SetValue( formatScaleValue( value ) ); } @@ -593,14 +599,11 @@ void PANEL_PREVIEW_3D_MODEL::onMouseWheelRot( wxMouseEvent& event ) if( event.GetWheelRotation() >= 0 ) step = -step; - double curr_value = EDA_UNIT_UTILS::UI::DoubleValueFromString( unityScale, EDA_UNITS::DEGREES, - textCtrl->GetValue() ); + double value = rotationFromString( textCtrl->GetValue() ); - curr_value += step; - curr_value = std::max( -MAX_ROTATION, curr_value ); - curr_value = std::min( curr_value, MAX_ROTATION ); + value += step; - textCtrl->SetValue( formatRotationValue( curr_value ) ); + textCtrl->SetValue( formatRotationValue( value ) ); } @@ -626,28 +629,24 @@ void PANEL_PREVIEW_3D_MODEL::onMouseWheelOffset( wxMouseEvent& event ) if( event.GetWheelRotation() >= 0 ) step_mm = -step_mm; - double curr_value_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, - textCtrl->GetValue() ) - / pcbIUScale.IU_PER_MM; + double value_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, textCtrl->GetValue() ) + / pcbIUScale.IU_PER_MM; - curr_value_mm += step_mm; - curr_value_mm = std::max( -MAX_OFFSET, curr_value_mm ); - curr_value_mm = std::min( curr_value_mm, MAX_OFFSET ); + value_mm += step_mm; + value_mm = std::max( -MAX_OFFSET, value_mm ); + value_mm = std::min( value_mm, MAX_OFFSET ); - textCtrl->SetValue( formatOffsetValue( curr_value_mm ) ); + textCtrl->SetValue( formatOffsetValue( value_mm ) ); } void PANEL_PREVIEW_3D_MODEL::onUnitsChanged( wxCommandEvent& aEvent ) { - double xoff_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, - xoff->GetValue() ) + double xoff_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, xoff->GetValue() ) / pcbIUScale.IU_PER_MM; - double yoff_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, - yoff->GetValue() ) + double yoff_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, yoff->GetValue() ) / pcbIUScale.IU_PER_MM; - double zoff_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, - zoff->GetValue() ) + double zoff_mm = EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, m_userUnits, zoff->GetValue() ) / pcbIUScale.IU_PER_MM; PCB_BASE_FRAME* frame = static_cast( aEvent.GetClientData() ); diff --git a/3d-viewer/dialogs/panel_preview_3d_model.h b/3d-viewer/dialogs/panel_preview_3d_model.h index d921410b1c..d748fd1f1a 100644 --- a/3d-viewer/dialogs/panel_preview_3d_model.h +++ b/3d-viewer/dialogs/panel_preview_3d_model.h @@ -163,6 +163,8 @@ private: wxString formatRotationValue( double aValue ); wxString formatOffsetValue( double aValue ); + void evalRotationValue( TEXT_CTRL_EVAL* aRotationControl ); + void View3DISO( wxCommandEvent& event ) override { m_currentCamera.ToggleProjection(); diff --git a/common/widgets/text_ctrl_eval.cpp b/common/widgets/text_ctrl_eval.cpp index ba096a9261..e89ff28e08 100644 --- a/common/widgets/text_ctrl_eval.cpp +++ b/common/widgets/text_ctrl_eval.cpp @@ -29,16 +29,12 @@ TEXT_CTRL_EVAL::TEXT_CTRL_EVAL( wxWindow* aParent, wxWindowID aId, const wxString& aValue, const wxPoint& aPos, const wxSize& aSize, long aStyle, const wxValidator& aValidator, const wxString& aName ) : - wxTextCtrl( aParent, aId, aValue, aPos, aSize, aStyle | wxTE_PROCESS_ENTER, aValidator, - aName ), + wxTextCtrl( aParent, aId, aValue, aPos, aSize, aStyle | wxTE_PROCESS_ENTER, aValidator, aName ), m_eval( EDA_UNITS::UNSCALED ) { - Connect( wxEVT_SET_FOCUS, wxFocusEventHandler( TEXT_CTRL_EVAL::onTextFocusGet ), nullptr, - this ); - Connect( wxEVT_KILL_FOCUS, wxFocusEventHandler( TEXT_CTRL_EVAL::onTextFocusLost ), nullptr, - this ); - Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( TEXT_CTRL_EVAL::onTextEnter ), nullptr, - this ); + Connect( wxEVT_SET_FOCUS, wxFocusEventHandler( TEXT_CTRL_EVAL::onTextFocusGet ), nullptr, this ); + Connect( wxEVT_KILL_FOCUS, wxFocusEventHandler( TEXT_CTRL_EVAL::onTextFocusLost ), nullptr, this ); + Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( TEXT_CTRL_EVAL::onTextEnter ), nullptr, this ); } @@ -62,7 +58,11 @@ void TEXT_CTRL_EVAL::onTextFocusGet( wxFocusEvent& aEvent ) void TEXT_CTRL_EVAL::onTextFocusLost( wxFocusEvent& aEvent ) { - evaluate(); + if( m_customEval.has_value() ) + m_customEval.value()( this ); + else + evaluate(); + aEvent.Skip(); } diff --git a/include/widgets/text_ctrl_eval.h b/include/widgets/text_ctrl_eval.h index e1c3c93df3..435876168c 100644 --- a/include/widgets/text_ctrl_eval.h +++ b/include/widgets/text_ctrl_eval.h @@ -49,6 +49,11 @@ public: { } + void SetCustomEval( std::function aCustomEval ) + { + m_customEval = std::move( aCustomEval ); + } + /** * Set a new value in evaluator buffer and display it in the wxTextCtrl. * @@ -66,6 +71,9 @@ protected: void onTextEnter( wxCommandEvent& aEvent ); void evaluate(); + +private: + std::optional> m_customEval; };