Files
Damjan Prerad b156372d59 Narrow tuning pattern area selection
Use generated child items for box and polygon hit testing so tuning
patterns select more tightly. Fall back to the generator outline only
when the pattern has no child board items.

Fixes https://gitlab.com/kicad/code/kicad/-/work_items/23384
2026-03-17 01:16:51 +01:00

575 lines
18 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Alex Shvartzkop <[email protected]>
* 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 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
*/
#include <eda_item.h>
#include <geometry/geometry_utils.h>
#include <pcb_base_edit_frame.h>
#include <router/pns_meander.h>
#include <router/pns_meander_placer_base.h>
#include <router/pns_router.h>
#include <router/pns_routing_settings.h>
namespace KIGFX
{
class VIEW;
class RENDER_SETTINGS;
}
enum LENGTH_TUNING_MODE
{
SINGLE,
DIFF_PAIR,
DIFF_PAIR_SKEW
};
class TUNING_STATUS_VIEW_ITEM : public EDA_ITEM
{
public:
TUNING_STATUS_VIEW_ITEM( PCB_BASE_EDIT_FRAME* aFrame );
wxString GetClass() const override;
#if defined(DEBUG)
void Show( int nestLevel, std::ostream& os ) const override;
#endif
VECTOR2I GetPosition() const override;
void SetPosition( const VECTOR2I& aPos ) override;
void SetMinMax( const double aMin, const double aMax );
void ClearMinMax();
void SetCurrent( const double aCurrent, const wxString& aLabel );
void SetIsTimeDomain( const bool aIsTimeDomain );
const BOX2I ViewBBox() const override;
std::vector<int> ViewGetLayers() const override;
void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override;
protected:
EDA_DRAW_FRAME* m_frame;
VECTOR2I m_pos;
double m_min;
double m_max;
double m_current;
wxString m_currentLabel;
wxString m_currentText;
wxString m_minText;
wxString m_maxText;
bool m_isTimeDomain;
};
class PCB_TUNING_PATTERN : public PCB_GENERATOR
{
public:
static const wxString GENERATOR_TYPE;
static const wxString DISPLAY_NAME;
PCB_TUNING_PATTERN( BOARD_ITEM* aParent = nullptr, PCB_LAYER_ID aLayer = F_Cu,
LENGTH_TUNING_MODE aMode = LENGTH_TUNING_MODE::SINGLE );
wxString GetGeneratorType() const override { return wxS( "tuning_pattern" ); }
wxString GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const override
{
return _( "Tuning Pattern" );
}
wxString GetFriendlyName() const override
{
return _( "Tuning Pattern" );
}
wxString GetPluralName() const override
{
return _( "Tuning Patterns" );
}
wxString GetCommitMessage() const override
{
return _( "Edit Tuning Pattern" );
}
BITMAPS GetMenuImage() const override
{
switch( m_tuningMode )
{
case SINGLE: return BITMAPS::ps_tune_length; break;
case DIFF_PAIR: return BITMAPS::ps_diff_pair_tune_length; break;
case DIFF_PAIR_SKEW: return BITMAPS::ps_diff_pair_tune_phase; break;
}
return BITMAPS::unknown;
}
static PCB_TUNING_PATTERN* CreateNew( GENERATOR_TOOL* aTool, PCB_BASE_EDIT_FRAME* aFrame,
BOARD_CONNECTED_ITEM* aStartItem,
LENGTH_TUNING_MODE aMode );
void EditStart( GENERATOR_TOOL* aTool, BOARD* aBoard, BOARD_COMMIT* aCommit ) override;
bool Update( GENERATOR_TOOL* aTool, BOARD* aBoard, BOARD_COMMIT* aCommit ) override;
void EditFinish( GENERATOR_TOOL* aTool, BOARD* aBoard, BOARD_COMMIT* aCommit ) override;
void EditCancel( GENERATOR_TOOL* aTool, BOARD* aBoard, BOARD_COMMIT* aCommit ) override;
void Remove( GENERATOR_TOOL* aTool, BOARD* aBoard, BOARD_COMMIT* aCommit ) override;
bool MakeEditPoints( EDIT_POINTS& points ) const override;
bool UpdateFromEditPoints( EDIT_POINTS& aEditPoints ) override;
bool UpdateEditPoints( EDIT_POINTS& aEditPoints ) override;
void Move( const VECTOR2I& aMoveVector ) override
{
m_origin += aMoveVector;
m_end += aMoveVector;
if( !this->HasFlag( IN_EDIT ) )
{
PCB_GROUP::Move( aMoveVector );
if( m_baseLine )
m_baseLine->Move( aMoveVector );
if( m_baseLineCoupled )
m_baseLineCoupled->Move( aMoveVector );
}
}
void Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle ) override
{
if( !this->HasFlag( IN_EDIT ) )
{
PCB_GENERATOR::Rotate( aRotCentre, aAngle );
RotatePoint( m_end, aRotCentre, aAngle );
if( m_baseLine )
m_baseLine->Rotate( aAngle, aRotCentre );
if( m_baseLineCoupled )
m_baseLineCoupled->Rotate( aAngle, aRotCentre );
}
}
void Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) override
{
if( !this->HasFlag( IN_EDIT ) )
{
PCB_GENERATOR::Flip( aCentre, aFlipDirection );
baseMirror( aCentre, aFlipDirection );
}
}
void Mirror( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) override
{
if( !this->HasFlag( IN_EDIT ) )
{
PCB_GENERATOR::Mirror( aCentre, aFlipDirection );
baseMirror( aCentre, aFlipDirection );
}
}
void SetLayer( PCB_LAYER_ID aLayer ) override
{
PCB_GENERATOR::SetLayer( aLayer );
for( BOARD_ITEM* item : GetBoardItems() )
item->SetLayer( aLayer );
}
PCB_LAYER_ID GetLayer() const override
{
for( BOARD_ITEM* item : GetBoardItems() )
{
if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
return track->GetLayer();
}
return PCB_GENERATOR::GetLayer();
}
const BOX2I GetBoundingBox() const override
{
return getOutline().BBox();
}
std::vector<int> ViewGetLayers() const override
{
return { LAYER_ANCHOR, GetLayer() };
}
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override
{
return getOutline().Collide( aPosition, aAccuracy );
}
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const override
{
bool anyItems = false;
for( BOARD_ITEM* item : GetBoardItems() )
{
anyItems = true;
if( aContained )
{
if( !item->HitTest( aRect, true, aAccuracy ) )
return false;
}
else if( item->HitTest( aRect, false, aAccuracy ) )
{
return true;
}
}
if( anyItems )
return aContained;
BOX2I sel = aRect;
if( aAccuracy )
sel.Inflate( aAccuracy );
// Convert selection box to polygon for accurate hit testing against the actual outline
SHAPE_LINE_CHAIN selPoly(
{
sel.GetOrigin(),
VECTOR2I( sel.GetRight(), sel.GetTop() ),
sel.GetEnd(),
VECTOR2I( sel.GetLeft(), sel.GetBottom() )
},
true );
return KIGEOM::ShapeHitTest( selPoly, getOutline(), aContained );
}
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override
{
bool anyItems = false;
for( BOARD_ITEM* item : GetBoardItems() )
{
anyItems = true;
if( aContained )
{
if( !item->HitTest( aPoly, true ) )
return false;
}
else if( item->HitTest( aPoly, false ) )
{
return true;
}
}
if( anyItems )
return aContained;
return KIGEOM::ShapeHitTest( aPoly, getOutline(), aContained );
}
const BOX2I ViewBBox() const override { return GetBoundingBox(); }
EDA_ITEM* Clone() const override { return new PCB_TUNING_PATTERN( *this ); }
void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override final;
const VECTOR2I& GetEnd() const { return m_end; }
void SetEnd( const VECTOR2I& aValue ) { m_end = aValue; }
int GetEndX() const { return m_end.x; }
void SetEndX( int aValue ) { m_end.x = aValue; }
int GetEndY() const { return m_end.y; }
void SetEndY( int aValue ) { m_end.y = aValue; }
int GetWidth() const
{
for( BOARD_ITEM* item : GetBoardItems() )
if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
return track->GetWidth();
return m_trackWidth;
}
void SetWidth( int aValue )
{
m_trackWidth = aValue;
for( BOARD_ITEM* item : GetBoardItems() )
if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
track->SetWidth( aValue );
}
int GetNetCode() const
{
for( BOARD_ITEM* item : GetBoardItems() )
if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
return bci->GetNetCode();
return 0;
}
void SetNetCode( int aNetCode )
{
if( BOARD* board = GetBoard() )
{
if( NETINFO_ITEM* net = board->FindNet( aNetCode ) )
m_lastNetName = net->GetNetname();
else
m_lastNetName.clear();
}
for( BOARD_ITEM* item : GetBoardItems() )
if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
bci->SetNetCode( aNetCode );
}
bool HasSolderMask() const
{
for( BOARD_ITEM* item : GetBoardItems() )
if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
return track->HasSolderMask();
return true;
}
void SetHasSolderMask( bool aVal )
{
for( BOARD_ITEM* item : GetBoardItems() )
if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
track->SetHasSolderMask( aVal );
}
std::optional<int> GetLocalSolderMaskMargin() const
{
for( BOARD_ITEM* item : GetBoardItems() )
if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
return track->GetLocalSolderMaskMargin();
return std::optional<int>();
}
void SetLocalSolderMaskMargin( std::optional<int> aMargin )
{
for( BOARD_ITEM* item : GetBoardItems() )
if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
track->SetLocalSolderMaskMargin( aMargin );
}
LENGTH_TUNING_MODE GetTuningMode() const { return m_tuningMode; }
PNS::ROUTER_MODE GetPNSMode()
{
switch( m_tuningMode )
{
case LENGTH_TUNING_MODE::SINGLE: return PNS::PNS_MODE_TUNE_SINGLE;
case LENGTH_TUNING_MODE::DIFF_PAIR: return PNS::PNS_MODE_TUNE_DIFF_PAIR;
case LENGTH_TUNING_MODE::DIFF_PAIR_SKEW: return PNS::PNS_MODE_TUNE_DIFF_PAIR_SKEW;
default: return PNS::PNS_MODE_TUNE_SINGLE;
}
}
PNS::MEANDER_SETTINGS& GetSettings() { return m_settings; }
int GetMinAmplitude() const { return m_settings.m_minAmplitude; }
void SetMinAmplitude( int aValue )
{
aValue = std::max( aValue, 0 );
m_settings.m_minAmplitude = aValue;
if( m_settings.m_maxAmplitude < m_settings.m_minAmplitude )
m_settings.m_maxAmplitude = m_settings.m_minAmplitude;
}
int GetMaxAmplitude() const { return m_settings.m_maxAmplitude; }
void SetMaxAmplitude( int aValue )
{
aValue = std::max( aValue, 0 );
m_settings.m_maxAmplitude = aValue;
if( m_settings.m_maxAmplitude < m_settings.m_minAmplitude )
m_settings.m_minAmplitude = m_settings.m_maxAmplitude;
}
// Update the initial side one time at EditStart based on m_end.
void UpdateSideFromEnd() { m_updateSideFromEnd = true; }
PNS::MEANDER_SIDE GetInitialSide() const { return m_settings.m_initialSide; }
void SetInitialSide( PNS::MEANDER_SIDE aValue ) { m_settings.m_initialSide = aValue; }
int GetSpacing() const { return m_settings.m_spacing; }
void SetSpacing( int aValue ) { m_settings.m_spacing = aValue; }
std::optional<int> GetTargetLength() const
{
if( m_settings.m_targetLength.Opt() == PNS::MEANDER_SETTINGS::LENGTH_UNCONSTRAINED )
return std::optional<int>();
else
return m_settings.m_targetLength.Opt();
}
void SetTargetLength( std::optional<int> aValue )
{
m_settings.m_isTimeDomain = false;
if( aValue.has_value() )
m_settings.SetTargetLength( aValue.value() );
else
m_settings.SetTargetLength( PNS::MEANDER_SETTINGS::LENGTH_UNCONSTRAINED );
}
std::optional<int> GetTargetDelay() const
{
if( m_settings.m_targetLengthDelay.Opt() == PNS::MEANDER_SETTINGS::DELAY_UNCONSTRAINED )
return std::optional<int>();
else
return m_settings.m_targetLengthDelay.Opt();
}
void SetTargetDelay( std::optional<int> aValue )
{
m_settings.m_isTimeDomain = true;
if( aValue.has_value() )
m_settings.SetTargetLengthDelay( aValue.value() );
else
m_settings.SetTargetLengthDelay( PNS::MEANDER_SETTINGS::DELAY_UNCONSTRAINED );
}
int GetTargetSkew() const { return m_settings.m_targetSkew.Opt(); }
void SetTargetSkew( int aValue ) { m_settings.SetTargetSkew( aValue ); }
int GetTargetSkewDelay() const { return m_settings.m_targetSkewDelay.Opt(); }
void SetTargetSkewDelay( int aValue ) { m_settings.SetTargetSkewDelay( aValue ); }
bool GetOverrideCustomRules() const { return m_settings.m_overrideCustomRules; }
void SetOverrideCustomRules( bool aOverride ) { m_settings.m_overrideCustomRules = aOverride; }
int GetCornerRadiusPercentage() const { return m_settings.m_cornerRadiusPercentage; }
void SetCornerRadiusPercentage( int aValue ) { m_settings.m_cornerRadiusPercentage = aValue; }
bool IsSingleSided() const { return m_settings.m_singleSided; }
void SetSingleSided( bool aValue ) { m_settings.m_singleSided = aValue; }
bool IsRounded() const { return m_settings.m_cornerStyle == PNS::MEANDER_STYLE_ROUND; }
void SetRounded( bool aFlag ) { m_settings.m_cornerStyle = aFlag ? PNS::MEANDER_STYLE_ROUND
: PNS::MEANDER_STYLE_CHAMFER; }
std::vector<std::pair<wxString, wxVariant>> GetRowData() override
{
std::vector<std::pair<wxString, wxVariant>> data = PCB_GENERATOR::GetRowData();
data.emplace_back( _HKI( "Net" ), m_lastNetName );
data.emplace_back( _HKI( "Tuning" ), m_tuningInfo );
return data;
}
const STRING_ANY_MAP GetProperties() const override;
void SetProperties( const STRING_ANY_MAP& aProps ) override;
void ShowPropertiesDialog( PCB_BASE_EDIT_FRAME* aEditFrame ) override;
std::vector<EDA_ITEM*> GetPreviewItems( GENERATOR_TOOL* aTool, PCB_BASE_EDIT_FRAME* aFrame,
bool aStatusItemsOnly = false ) override;
void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;
protected:
void swapData( BOARD_ITEM* aImage ) override
{
wxASSERT( aImage->Type() == PCB_GENERATOR_T );
std::swap( *this, *static_cast<PCB_TUNING_PATTERN*>( aImage ) );
}
bool recoverBaseline( PNS::ROUTER* aRouter );
bool baselineValid();
bool initBaseLine( PNS::ROUTER* aRouter, int aPNSLayer, BOARD* aBoard, VECTOR2I& aStart,
VECTOR2I& aEnd, NETINFO_ITEM* aNet,
std::optional<SHAPE_LINE_CHAIN>& aBaseLine );
bool initBaseLines( PNS::ROUTER* aRouter, int aPNSLayer, BOARD* aBoard );
bool removeToBaseline( PNS::ROUTER* aRouter, int aPNSLayer, SHAPE_LINE_CHAIN& aBaseLine );
bool resetToBaseline( GENERATOR_TOOL* aTool, int aPNSLayer, SHAPE_LINE_CHAIN& aBaseLine,
bool aPrimary );
SHAPE_LINE_CHAIN getOutline() const;
void baseMirror( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
{
PCB_GENERATOR::baseMirror( aCentre, aFlipDirection );
if( m_baseLine )
{
m_baseLine->Mirror( aCentre, aFlipDirection );
m_origin = m_baseLine->CPoint( 0 );
m_end = m_baseLine->CLastPoint();
}
if( m_baseLineCoupled )
m_baseLineCoupled->Mirror( aCentre, aFlipDirection );
if( m_settings.m_initialSide == PNS::MEANDER_SIDE_RIGHT )
m_settings.m_initialSide = PNS::MEANDER_SIDE_LEFT;
else
m_settings.m_initialSide = PNS::MEANDER_SIDE_RIGHT;
}
protected:
VECTOR2I m_end;
PNS::MEANDER_SETTINGS m_settings;
std::optional<SHAPE_LINE_CHAIN> m_baseLine;
std::optional<SHAPE_LINE_CHAIN> m_baseLineCoupled;
int m_trackWidth;
int m_diffPairGap;
LENGTH_TUNING_MODE m_tuningMode;
wxString m_lastNetName;
wxString m_tuningInfo;
long long m_tuningLength;
PNS::MEANDER_PLACER_BASE::TUNING_STATUS m_tuningStatus;
bool m_updateSideFromEnd;
};