From 60046a1bccee92e569d5e44192e091bae79e6cda Mon Sep 17 00:00:00 2001 From: Peter Montgomery Date: Fri, 2 Oct 2020 00:56:52 +0000 Subject: [PATCH] Dynamically scale cross-probe zooming CHANGED: When doing cross-probe zooming, KiCad zooms the selected component to fill the screen. This makes it hard to understand the part in context so users would have to always zoom out manually. This commit uses the default text height in Eeschema and Pcbnew as a constant to compare the height of selected parts or components against. This lets the code determine how big the part is and scale the zoom wider accordingly. Big parts get less scaling and small part get more. NOTE: There is a little bit of debug code present. There are #ifdefs to let developers easily swap in the original KiCad zoom for comparison. There is also a bool added that can force each program to always perform the new calculated zoom since KiCad ignores zooms that are close to the current value. This debug code can be removed later as desired. --- eeschema/cross-probing.cpp | 120 +++++++++++++++++++++++++++++++++++-- pcbnew/cross-probing.cpp | 120 +++++++++++++++++++++++++++++++++++-- 2 files changed, 229 insertions(+), 11 deletions(-) diff --git a/eeschema/cross-probing.cpp b/eeschema/cross-probing.cpp index fed95030a4..f61229b7f8 100644 --- a/eeschema/cross-probing.cpp +++ b/eeschema/cross-probing.cpp @@ -97,6 +97,7 @@ SCH_ITEM* SCH_EDITOR_CONTROL::FindComponentAndItem( const wxString& aReference, CROSS_PROBING_SETTINGS& crossProbingSettings = m_frame->eeconfig()->m_CrossProbing; + if( component ) { if( *sheetWithComponentFound != m_frame->GetCurrentSheet() ) @@ -106,7 +107,7 @@ SCH_ITEM* SCH_EDITOR_CONTROL::FindComponentAndItem( const wxString& aReference, } wxPoint delta; - pos -= component->GetPosition(); + pos -= component->GetPosition(); delta = component->GetTransform().TransformCoordinate( pos ); pos = delta + component->GetPosition(); @@ -117,20 +118,129 @@ SCH_ITEM* SCH_EDITOR_CONTROL::FindComponentAndItem( const wxString& aReference, if( crossProbingSettings.zoom_to_fit ) { +//#define COMP_1_TO_1_RATIO // Un-comment for normal KiCad full screen zoom cross-probe +#ifdef COMP_1_TO_1_RATIO // Pass "false" to only include visible fields of component in bbox calculations - EDA_RECT bbox = component->GetBoundingBox( false ); - + EDA_RECT bbox = component->GetBoundingBox( false ); wxSize bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize(); VECTOR2D screenSize = getView()->GetViewport().GetSize(); + // NOTE: The 1:1 here is using the default KiCad sizing, which adds a margin of 20% + screenSize.x = std::max( 10.0, screenSize.x ); screenSize.y = std::max( 10.0, screenSize.y ); - double ratio = std::max( fabs( bbSize.x / screenSize.x ), - fabs( bbSize.y / screenSize.y ) ); + double ratio = std::max( + fabs( bbSize.x / screenSize.x ), fabs( bbSize.y / screenSize.y ) ); // Try not to zoom on every cross-probe; it gets very noisy if( ratio < 0.5 || ratio > 1.0 ) getView()->SetScale( getView()->GetScale() / ratio ); +#endif // COMP_1_TO_1_RATIO + +#ifndef COMP_1_TO_1_RATIO // Do the scaled zoom + // Pass "false" to only include visible fields of component in bbox calculations + EDA_RECT bbox = component->GetBoundingBox( false ); + wxSize bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize(); + VECTOR2D screenSize = getView()->GetViewport().GetSize(); + + // This code tries to come up with a zoom factor that doesn't simply zoom in + // to the cross probed component, but instead shows a reasonable amount of the + // circuit around it to provide context. This reduces or eliminates the need + // to manually change the zoom because it's too close. + + // Using the default text height as a constant to compare against, use the + // height of the bounding box of visible items for a footprint to figure out + // if this is a big symbol (like a processor) or a small symbol (like a resistor). + // This ratio is not useful by itself as a scaling factor. It must be "bent" to + // provide good scaling at varying component sizes. Bigger components need less + // scaling than small ones. + double currTextHeight = Mils2iu( DEFAULT_TEXT_SIZE ); + + double compRatio = bbSize.y / currTextHeight; // Ratio of component to text height + double compRatioBent = 1.0; + + // LUT to scale zoom ratio to provide reasonable schematic context. Must work + // with symbols of varying sizes (e.g. 0402 package and 200 pin BGA). + // "first" is used as the input and "second" as the output + // + // "first" = compRatio (symbol height / default text height) + // "second" = Amount to scale ratio by + std::vector> lut + { + {1.25, 16}, // 32 + {2.5, 12}, //24 + {5, 8}, // 16 + {6, 6}, // + {10, 4}, //8 + {20, 2}, //4 + {40, 1.5}, // 2 + {100, 1} + }; + + std::vector>::iterator it; + + compRatioBent = + lut.back().second; // Large component default is last LUT entry (1:1) + + // Use LUT to do linear interpolation of "compRatio" within "first", then + // use that result to linearly interpolate "second" which gives the scaling + // factor needed. + + if( compRatio >= lut.front().first ) + { + for( it = lut.begin(); it < lut.end() - 1; it++ ) + { + if( it->first <= compRatio && next( it )->first >= compRatio ) + { + + double diffx = compRatio - it->first; + double diffn = next( it )->first - it->first; + + compRatioBent = it->second + + ( next( it )->second - it->second ) * diffx / diffn; + break; // We have our interpolated value + } + } + } + else + compRatioBent = lut.front().second; // Small component default is first entry + + // This is similar to the original KiCad code that scaled the zoom to make sure + // components were visible on screen. It's simply a ratio of screen size to component + // size, and its job is to zoom in to make the component fullscreen. Earlier in the + // code the component BBox is given a 20% margin to add some breathing room. We compare + // the height of this enlarged component bbox to the default text height. If a + // component will end up with the sides clipped, we adjust later to make sure it fits + // on screen. + screenSize.x = std::max( 10.0, screenSize.x ); + screenSize.y = std::max( 10.0, screenSize.y ); + double ratio = std::max( -1.0, fabs( bbSize.y / screenSize.y ) ); + // Original KiCad code for how much to scale the zoom + double kicadRatio = std::max( fabs( bbSize.x / screenSize.x ), + fabs( bbSize.y / screenSize.y ) ); + + // If the width of the part we're probing is bigger than what the screen width will be + // after the zoom, then punt and use the KiCad zoom algorithm since it guarantees the + // part's width will be encompassed within the screen. + + if( bbSize.x > screenSize.x * ratio * compRatioBent ) + { + ratio = kicadRatio; // Use standard KiCad zoom for parts too wide to fit on screen + compRatioBent = 1.0; // Reset so we don't modify the "KiCad" ratio + wxLogTrace( "CROSS_PROBE_SCALE", + "Part TOO WIDE for screen. Using normal KiCad zoom ratio: %1.5f", + ratio ); + } + + // Now that "compRatioBent" holds our final scaling factor we apply it to the original + // fullscreen zoom ratio to arrive at the final ratio itself. + ratio *= compRatioBent; + + bool alwaysZoom = false; // DEBUG - allows us to minimize zooming or not + // Try not to zoom on every cross-probe; it gets very noisy + if( ( ratio < 0.5 || ratio > 1.0 ) || alwaysZoom ) + getView()->SetScale( getView()->GetScale() / ratio ); +#endif // ifndef COMP_1_TO_1_RATIO } } } diff --git a/pcbnew/cross-probing.cpp b/pcbnew/cross-probing.cpp index bc8a13e0ee..8148d69a76 100644 --- a/pcbnew/cross-probing.cpp +++ b/pcbnew/cross-probing.cpp @@ -214,7 +214,7 @@ void PCB_EDIT_FRAME::ExecuteRemoteCommand( const char* cmdline ) if( module ) { - bbox = module->GetBoundingBox(); + bbox = module->GetBoundingBox( false ); // No invisible text in bbox calc if( pad ) m_toolManager->RunAction( PCB_ACTIONS::highlightItem, true, (void*) pad ); @@ -236,8 +236,9 @@ void PCB_EDIT_FRAME::ExecuteRemoteCommand( const char* cmdline ) pcb->HighLightON(); - auto merge_area = [netcode, &bbox]( BOARD_CONNECTED_ITEM* aItem ) - { + auto merge_area = + [netcode, &bbox]( BOARD_CONNECTED_ITEM* aItem ) + { if( aItem->GetNetCode() == netcode ) { if( bbox.GetWidth() == 0 ) @@ -267,17 +268,124 @@ void PCB_EDIT_FRAME::ExecuteRemoteCommand( const char* cmdline ) if( crossProbingSettings.center_on_items && bbox.GetWidth() > 0 && bbox.GetHeight() > 0 ) { - auto bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize(); + +//#define DEFAULT_PCBNEW_CODE // Un-comment for normal full zoom KiCad algorithm +#ifdef DEFAULT_PCBNEW_CODE + auto bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize(); auto screenSize = view->ToWorld( GetCanvas()->GetClientSize(), false ); + // The "fabs" on x ensures the right answer when the view is flipped screenSize.x = std::max( 10.0, fabs( screenSize.x ) ); screenSize.y = std::max( 10.0, screenSize.y ); - double ratio = std::max( fabs( bbSize.x / screenSize.x ), - fabs( bbSize.y / screenSize.y ) ); + double ratio = std::max( fabs( bbSize.x / screenSize.x ), fabs( bbSize.y / screenSize.y ) ); // Try not to zoom on every cross-probe; it gets very noisy if( crossProbingSettings.zoom_to_fit && ( ratio < 0.5 || ratio > 1.0 ) ) view->SetScale( view->GetScale() / ratio ); +#endif // DEFAULT_PCBNEW_CODE + +#ifndef DEFAULT_PCBNEW_CODE // Do the scaled zoom + auto bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize(); + auto screenSize = view->ToWorld( GetCanvas()->GetClientSize(), false ); + + // This code tries to come up with a zoom factor that doesn't simply zoom in + // to the cross probed component, but instead shows a reasonable amount of the + // circuit around it to provide context. This reduces or eliminates the need + // to manually change the zoom because it's too close. + + // Using the default text height as a constant to compare against, use the + // height of the bounding box of visible items for a footprint to figure out + // if this is a big footprint (like a processor) or a small footprint (like a resistor). + // This ratio is not useful by itself as a scaling factor. It must be "bent" to + // provide good scaling at varying component sizes. Bigger components need less + // scaling than small ones. + double currTextHeight = Millimeter2iu( DEFAULT_TEXT_SIZE ); + + double compRatio = bbSize.y / currTextHeight; // Ratio of component to text height + double compRatioBent = 1.0; // This will end up as the scaling factor we apply to "ratio" + + // This is similar to the original KiCad code that scaled the zoom to make sure components + // were visible on screen. It's simply a ratio of screen size to component size, and its + // job is to zoom in to make the component fullscreen. Earlier in the code the + // component BBox is given a 20% margin to add some breathing room. We compare + // the height of this enlarged component bbox to the default text height. If a component + // will end up with the sides clipped, we adjust later to make sure it fits on screen. + // + // The "fabs" on x ensures the right answer when the view is flipped + screenSize.x = std::max( 10.0, fabs( screenSize.x ) ); + screenSize.y = std::max( 10.0, screenSize.y ); + double ratio = std::max( -1.0, fabs( bbSize.y / screenSize.y ) ); + // Original KiCad code for how much to scale the zoom + double kicadRatio = std::max( fabs( bbSize.x / screenSize.x ), + fabs( bbSize.y / screenSize.y ) ); + + // LUT to scale zoom ratio to provide reasonable schematic context. Must work + // with footprints of varying sizes (e.g. 0402 package and 200 pin BGA). + // "first" is used as the input and "second" as the output + // + // "first" = compRatio (footprint height / default text height) + // "second" = Amount to scale ratio by + std::vector> lut{ + { 1, 8 }, + { 1.5, 5 }, + { 3, 3 }, + { 4.5, 2.5 }, + { 8, 2.0 }, + { 12, 1.7 }, + { 16, 1.5 }, + { 24, 1.3 }, + { 32, 1.0 }, + }; + + + std::vector>::iterator it; + + compRatioBent = lut.back().second; // Large component default + + if( compRatio >= lut.front().first ) + { + // Use LUT to do linear interpolation of "compRatio" within "first", then + // use that result to linearly interpolate "second" which gives the scaling + // factor needed. + + for( it = lut.begin(); it < lut.end() - 1; it++ ) + { + if( it->first <= compRatio && next( it )->first >= compRatio ) + { + double diffx = compRatio - it->first; + double diffn = next( it )->first - it->first; + + compRatioBent = + it->second + ( next( it )->second - it->second ) * diffx / diffn; + break; // We have our interpolated value + } + } + } + else + compRatioBent = lut.front().second; // Small component default + + // If the width of the part we're probing is bigger than what the screen width will be + // after the zoom, then punt and use the KiCad zoom algorithm since it guarantees the + // part's width will be encompassed within the screen. This will apply to parts that are + // much wider than they are tall. + + if( bbSize.x > screenSize.x * ratio * compRatioBent ) + { + ratio = kicadRatio; // Use standard KiCad zoom algorithm for parts too wide to fit screen + compRatioBent = 1.0; // Reset so we don't modify the "KiCad" ratio + wxLogTrace( "CROSS_PROBE_SCALE", + "Part TOO WIDE for screen. Using normal KiCad zoom ratio: %1.5f", ratio ); + } + + // Now that "compRatioBent" holds our final scaling factor we apply it to the original + // fullscreen zoom ratio to arrive at the final ratio itself. + ratio *= compRatioBent; + + bool alwaysZoom = false; // DEBUG - allows us to minimize zooming or not + // Try not to zoom on every cross-probe; it gets very noisy + if( ( ratio < 0.5 || ratio > 1.0 ) || alwaysZoom ) + view->SetScale( view->GetScale() / ratio ); +#endif // ifndef DEFAULT_PCBNEW_CODE view->SetCenter( bbox.Centre() ); }