515 lines
14 KiB
C++
515 lines
14 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2014 CERN
|
|
* Copyright (C) 2018-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
|
*
|
|
* 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 <functional>
|
|
#include <macros.h>
|
|
#include <gal/graphics_abstraction_layer.h>
|
|
#include <sch_item.h>
|
|
#include <sch_line.h>
|
|
#include <sch_painter.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <settings/app_settings.h>
|
|
#include <trigo.h>
|
|
#include <view/view.h>
|
|
#include "ee_grid_helper.h"
|
|
|
|
|
|
EE_GRID_HELPER::EE_GRID_HELPER( TOOL_MANAGER* aToolMgr ) :
|
|
GRID_HELPER( aToolMgr )
|
|
{
|
|
KIGFX::VIEW* view = m_toolMgr->GetView();
|
|
|
|
m_viewAxis.SetSize( 20000 );
|
|
m_viewAxis.SetStyle( KIGFX::ORIGIN_VIEWITEM::CROSS );
|
|
m_viewAxis.SetColor( COLOR4D( 0.0, 0.1, 0.4, 0.8 ) );
|
|
m_viewAxis.SetDrawAtZero( true );
|
|
view->Add( &m_viewAxis );
|
|
view->SetVisible( &m_viewAxis, false );
|
|
|
|
m_viewSnapPoint.SetStyle( KIGFX::ORIGIN_VIEWITEM::CIRCLE_CROSS );
|
|
m_viewSnapPoint.SetColor( COLOR4D( 0.0, 0.1, 0.4, 1.0 ) );
|
|
m_viewSnapPoint.SetDrawAtZero( true );
|
|
view->Add( &m_viewSnapPoint );
|
|
view->SetVisible( &m_viewSnapPoint, false );
|
|
|
|
m_viewSnapLine.SetStyle( KIGFX::ORIGIN_VIEWITEM::DASH_LINE );
|
|
m_viewSnapLine.SetColor( COLOR4D( 0.33, 0.55, 0.95, 1.0 ) );
|
|
m_viewSnapLine.SetDrawAtZero( true );
|
|
view->Add( &m_viewSnapLine );
|
|
view->SetVisible( &m_viewSnapLine, false );
|
|
}
|
|
|
|
|
|
EE_GRID_HELPER::~EE_GRID_HELPER()
|
|
{
|
|
KIGFX::VIEW* view = m_toolMgr->GetView();
|
|
|
|
view->Remove( &m_viewAxis );
|
|
view->Remove( &m_viewSnapPoint );
|
|
view->Remove( &m_viewSnapLine );
|
|
}
|
|
|
|
|
|
VECTOR2I EE_GRID_HELPER::BestDragOrigin( const VECTOR2I& aMousePos, GRID_HELPER_GRIDS aGrid,
|
|
const EE_SELECTION& aItems )
|
|
{
|
|
clearAnchors();
|
|
|
|
// If we're working with any connectable objects, skip non-connectable objects
|
|
// since they are often off-grid, e.g. text anchors
|
|
bool hasConnectables = false;
|
|
|
|
for( EDA_ITEM* item : aItems )
|
|
{
|
|
GRID_HELPER_GRIDS grid = GetItemGrid( static_cast<SCH_ITEM*>( item ) );
|
|
if( grid == GRID_CONNECTABLE || grid == GRID_WIRES )
|
|
{
|
|
hasConnectables = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for( EDA_ITEM* item : aItems )
|
|
computeAnchors( static_cast<SCH_ITEM*>( item ), aMousePos, true, !hasConnectables );
|
|
|
|
double worldScale = m_toolMgr->GetView()->GetGAL()->GetWorldScale();
|
|
double lineSnapMinCornerDistance = 50.0 / worldScale;
|
|
|
|
ANCHOR* nearestOutline = nearestAnchor( aMousePos, OUTLINE, aGrid );
|
|
ANCHOR* nearestCorner = nearestAnchor( aMousePos, CORNER, aGrid );
|
|
ANCHOR* nearestOrigin = nearestAnchor( aMousePos, ORIGIN, aGrid );
|
|
ANCHOR* best = nullptr;
|
|
double minDist = std::numeric_limits<double>::max();
|
|
|
|
if( nearestOrigin )
|
|
{
|
|
minDist = nearestOrigin->Distance( aMousePos );
|
|
best = nearestOrigin;
|
|
}
|
|
|
|
if( nearestCorner )
|
|
{
|
|
double dist = nearestCorner->Distance( aMousePos );
|
|
|
|
if( dist < minDist )
|
|
{
|
|
minDist = dist;
|
|
best = nearestCorner;
|
|
}
|
|
}
|
|
|
|
if( nearestOutline )
|
|
{
|
|
double dist = nearestOutline->Distance( aMousePos );
|
|
|
|
if( minDist > lineSnapMinCornerDistance && dist < minDist )
|
|
best = nearestOutline;
|
|
}
|
|
|
|
return best ? best->pos : aMousePos;
|
|
}
|
|
|
|
|
|
VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GRIDS aGrid,
|
|
SCH_ITEM* aSkip )
|
|
{
|
|
EE_SELECTION skipItems;
|
|
skipItems.Add( aSkip );
|
|
|
|
return BestSnapAnchor( aOrigin, aGrid, skipItems );
|
|
}
|
|
|
|
|
|
VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GRIDS aGrid,
|
|
const EE_SELECTION& aSkip )
|
|
{
|
|
constexpr int snapRange = SNAP_RANGE * schIUScale.IU_PER_MILS;
|
|
|
|
VECTOR2I pt = aOrigin;
|
|
VECTOR2I snapDist( snapRange, snapRange );
|
|
bool snapLineX = false;
|
|
bool snapLineY = false;
|
|
bool snapPoint = false;
|
|
bool gridChecked = false;
|
|
|
|
BOX2I bb( VECTOR2I( aOrigin.x - snapRange / 2, aOrigin.y - snapRange / 2 ),
|
|
VECTOR2I( snapRange, snapRange ) );
|
|
|
|
clearAnchors();
|
|
|
|
for( SCH_ITEM* item : queryVisible( bb, aSkip ) )
|
|
computeAnchors( item, aOrigin );
|
|
|
|
ANCHOR* nearest = nearestAnchor( aOrigin, SNAPPABLE, aGrid );
|
|
VECTOR2I nearestGrid = Align( aOrigin, aGrid );
|
|
|
|
if( m_enableSnapLine && m_snapItem && m_skipPoint != VECTOR2I( m_viewSnapLine.GetPosition() ) )
|
|
{
|
|
if( std::abs( m_viewSnapLine.GetPosition().x - aOrigin.x ) < snapDist.x )
|
|
{
|
|
pt.x = m_viewSnapLine.GetPosition().x;
|
|
snapDist.x = std::abs( m_viewSnapLine.GetPosition().x - aOrigin.x );
|
|
snapLineX = true;
|
|
}
|
|
|
|
if( std::abs( m_viewSnapLine.GetPosition().y - aOrigin.y ) < snapDist.y )
|
|
{
|
|
pt.y = m_viewSnapLine.GetPosition().y;
|
|
snapDist.y = std::abs( m_viewSnapLine.GetPosition().y - aOrigin.y );
|
|
snapLineY = true;
|
|
}
|
|
|
|
if( canUseGrid() && std::abs( nearestGrid.x - aOrigin.x ) < snapDist.x )
|
|
{
|
|
pt.x = nearestGrid.x;
|
|
snapDist.x = std::abs( nearestGrid.x - aOrigin.x );
|
|
snapLineX = false;
|
|
}
|
|
|
|
if( canUseGrid() && std::abs( nearestGrid.y - aOrigin.y ) < snapDist.y )
|
|
{
|
|
pt.y = nearestGrid.y;
|
|
snapDist.y = std::abs( nearestGrid.y - aOrigin.y );
|
|
snapLineY = false;
|
|
}
|
|
|
|
gridChecked = true;
|
|
}
|
|
|
|
if( m_enableSnap && nearest && nearest->Distance( aOrigin ) < snapDist.EuclideanNorm() )
|
|
{
|
|
|
|
if( canUseGrid() && ( nearestGrid - aOrigin ).EuclideanNorm() < snapDist.EuclideanNorm() )
|
|
{
|
|
pt = nearestGrid;
|
|
snapDist.x = std::abs( nearestGrid.x - aOrigin.x );
|
|
snapDist.y = std::abs( nearestGrid.y - aOrigin.y );
|
|
snapPoint = false;
|
|
}
|
|
else
|
|
{
|
|
pt = nearest->pos;
|
|
snapDist.x = std::abs( nearest->pos.x - aOrigin.x );
|
|
snapDist.y = std::abs( nearest->pos.y - aOrigin.y );
|
|
snapPoint = true;
|
|
}
|
|
|
|
snapLineX = snapLineY = false;
|
|
gridChecked = true;
|
|
}
|
|
|
|
if( canUseGrid() && !gridChecked )
|
|
pt = nearestGrid;
|
|
|
|
if( snapLineX || snapLineY )
|
|
{
|
|
m_viewSnapLine.SetEndPosition( pt );
|
|
|
|
if( m_toolMgr->GetView()->IsVisible( &m_viewSnapLine ) )
|
|
m_toolMgr->GetView()->Update( &m_viewSnapLine, KIGFX::GEOMETRY );
|
|
else
|
|
m_toolMgr->GetView()->SetVisible( &m_viewSnapLine, true );
|
|
}
|
|
else if( snapPoint )
|
|
{
|
|
m_snapItem = nearest;
|
|
m_viewSnapPoint.SetPosition( pt );
|
|
m_viewSnapLine.SetPosition( pt );
|
|
|
|
m_toolMgr->GetView()->SetVisible( &m_viewSnapLine, false );
|
|
|
|
if( m_toolMgr->GetView()->IsVisible( &m_viewSnapPoint ) )
|
|
m_toolMgr->GetView()->Update( &m_viewSnapPoint, KIGFX::GEOMETRY);
|
|
else
|
|
m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, true );
|
|
}
|
|
else
|
|
{
|
|
m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false );
|
|
m_toolMgr->GetView()->SetVisible( &m_viewSnapLine, false );
|
|
}
|
|
|
|
return pt;
|
|
}
|
|
|
|
|
|
VECTOR2D EE_GRID_HELPER::GetGridSize( GRID_HELPER_GRIDS aGrid ) const
|
|
{
|
|
const GRID_SETTINGS& grid = m_toolMgr->GetSettings()->m_Window.grid;
|
|
int idx = -1;
|
|
|
|
VECTOR2D g = m_toolMgr->GetView()->GetGAL()->GetGridSize();
|
|
|
|
if( !grid.overrides_enabled )
|
|
return g;
|
|
|
|
switch( aGrid )
|
|
{
|
|
case GRID_CONNECTABLE:
|
|
if( grid.override_connected )
|
|
idx = grid.override_connected_idx;
|
|
|
|
break;
|
|
|
|
case GRID_WIRES:
|
|
if( grid.override_wires )
|
|
idx = grid.override_wires_idx;
|
|
|
|
break;
|
|
|
|
case GRID_TEXT:
|
|
if( grid.override_text )
|
|
idx = grid.override_text_idx;
|
|
|
|
break;
|
|
|
|
case GRID_GRAPHICS:
|
|
if( grid.override_graphics )
|
|
idx = grid.override_graphics_idx;
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if( idx >= 0 && idx < (int) grid.grids.size() )
|
|
g = grid.grids[idx].ToDouble( schIUScale );
|
|
|
|
return g;
|
|
}
|
|
|
|
|
|
SCH_ITEM* EE_GRID_HELPER::GetSnapped() const
|
|
{
|
|
if( !m_snapItem )
|
|
return nullptr;
|
|
|
|
return static_cast<SCH_ITEM*>( m_snapItem->item );
|
|
}
|
|
|
|
|
|
std::set<SCH_ITEM*> EE_GRID_HELPER::queryVisible( const BOX2I& aArea,
|
|
const EE_SELECTION& aSkipList ) const
|
|
{
|
|
std::set<SCH_ITEM*> items;
|
|
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> selectedItems;
|
|
|
|
KIGFX::VIEW* view = m_toolMgr->GetView();
|
|
|
|
view->Query( aArea, selectedItems );
|
|
|
|
for( const KIGFX::VIEW::LAYER_ITEM_PAIR& it : selectedItems )
|
|
{
|
|
SCH_ITEM* item = static_cast<SCH_ITEM*>( it.first );
|
|
|
|
// The item must be visible and on an active layer
|
|
if( view->IsVisible( item ) && item->ViewGetLOD( it.second, view ) < view->GetScale() )
|
|
items.insert ( item );
|
|
}
|
|
|
|
for( EDA_ITEM* skipItem : aSkipList )
|
|
items.erase( static_cast<SCH_ITEM*>( skipItem ) );
|
|
|
|
return items;
|
|
}
|
|
|
|
|
|
GRID_HELPER_GRIDS EE_GRID_HELPER::GetSelectionGrid( const SELECTION& aSelection ) const
|
|
{
|
|
GRID_HELPER_GRIDS grid = GetItemGrid( aSelection.Front() );
|
|
|
|
// Find the largest grid of all the items and use that
|
|
for( EDA_ITEM* item : aSelection )
|
|
{
|
|
GRID_HELPER_GRIDS itemGrid = GetItemGrid( item );
|
|
|
|
if( GetGridSize( itemGrid ) > GetGridSize( grid ) )
|
|
grid = itemGrid;
|
|
}
|
|
|
|
return grid;
|
|
}
|
|
|
|
|
|
GRID_HELPER_GRIDS EE_GRID_HELPER::GetItemGrid( const EDA_ITEM* aItem ) const
|
|
{
|
|
if( !aItem )
|
|
return GRID_CURRENT;
|
|
|
|
switch( aItem->Type() )
|
|
{
|
|
case LIB_SYMBOL_T:
|
|
case LIB_PIN_T:
|
|
case SCH_SYMBOL_T:
|
|
case SCH_PIN_T:
|
|
case SCH_SHEET_PIN_T:
|
|
case SCH_SHEET_T:
|
|
case SCH_NO_CONNECT_T:
|
|
case SCH_GLOBAL_LABEL_T:
|
|
case SCH_HIER_LABEL_T:
|
|
case SCH_LABEL_T:
|
|
case SCH_DIRECTIVE_LABEL_T:
|
|
return GRID_CONNECTABLE;
|
|
|
|
case LIB_FIELD_T:
|
|
case SCH_FIELD_T:
|
|
case LIB_TEXT_T:
|
|
case SCH_TEXT_T:
|
|
return GRID_TEXT;
|
|
|
|
case LIB_SHAPE_T:
|
|
case SCH_SHAPE_T:
|
|
// The text box's border lines are what need to be on the graphic grid
|
|
case LIB_TEXTBOX_T:
|
|
case SCH_TEXTBOX_T:
|
|
case SCH_BITMAP_T:
|
|
return GRID_GRAPHICS;
|
|
|
|
case SCH_JUNCTION_T:
|
|
return GRID_WIRES;
|
|
|
|
case SCH_LINE_T:
|
|
if( static_cast<const SCH_LINE*>( aItem )->IsConnectable() )
|
|
return GRID_WIRES;
|
|
else
|
|
return GRID_GRAPHICS;
|
|
|
|
case SCH_BUS_BUS_ENTRY_T:
|
|
case SCH_BUS_WIRE_ENTRY_T:
|
|
return GRID_WIRES;
|
|
|
|
default:
|
|
return GRID_CURRENT;
|
|
}
|
|
}
|
|
|
|
|
|
void EE_GRID_HELPER::computeAnchors( SCH_ITEM *aItem, const VECTOR2I &aRefPos, bool aFrom,
|
|
bool aIncludeText )
|
|
{
|
|
bool isGraphicLine =
|
|
aItem->Type() == SCH_LINE_T && static_cast<SCH_LINE*>( aItem )->IsGraphicLine();
|
|
|
|
switch( aItem->Type() )
|
|
{
|
|
case SCH_TEXT_T:
|
|
case SCH_TEXTBOX_T:
|
|
case SCH_FIELD_T:
|
|
{
|
|
if( aIncludeText )
|
|
addAnchor( aItem->GetPosition(), ORIGIN, aItem );
|
|
|
|
break;
|
|
}
|
|
|
|
case SCH_SYMBOL_T:
|
|
case SCH_SHEET_T:
|
|
addAnchor( aItem->GetPosition(), ORIGIN, aItem );
|
|
KI_FALLTHROUGH;
|
|
|
|
case SCH_JUNCTION_T:
|
|
case SCH_NO_CONNECT_T:
|
|
case SCH_LINE_T:
|
|
// Don't add anchors for graphic lines unless we're including text,
|
|
// they may be on a non-connectable grid
|
|
if( isGraphicLine && !aIncludeText )
|
|
break;
|
|
|
|
KI_FALLTHROUGH;
|
|
case SCH_GLOBAL_LABEL_T:
|
|
case SCH_HIER_LABEL_T:
|
|
case SCH_LABEL_T:
|
|
case SCH_DIRECTIVE_LABEL_T:
|
|
case SCH_BUS_WIRE_ENTRY_T:
|
|
case SCH_SHEET_PIN_T:
|
|
{
|
|
std::vector<VECTOR2I> pts = aItem->GetConnectionPoints();
|
|
|
|
for( const VECTOR2I& pt : pts )
|
|
addAnchor( VECTOR2I( pt ), SNAPPABLE | CORNER, aItem );
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Don't add anchors for graphic lines unless we're including text,
|
|
// they may be on a non-connectable grid
|
|
if( aItem->Type() == SCH_LINE_T && ( aIncludeText || !isGraphicLine ) )
|
|
{
|
|
SCH_LINE* line = static_cast<SCH_LINE*>( aItem );
|
|
VECTOR2I pt = Align( aRefPos );
|
|
|
|
if( line->GetStartPoint().x == line->GetEndPoint().x )
|
|
{
|
|
VECTOR2I possible( line->GetStartPoint().x, pt.y );
|
|
|
|
if( TestSegmentHit( possible, line->GetStartPoint(), line->GetEndPoint(), 0 ) )
|
|
addAnchor( possible, SNAPPABLE | VERTICAL, aItem );
|
|
}
|
|
else if( line->GetStartPoint().y == line->GetEndPoint().y )
|
|
{
|
|
VECTOR2I possible( pt.x, line->GetStartPoint().y );
|
|
|
|
if( TestSegmentHit( possible, line->GetStartPoint(), line->GetEndPoint(), 0 ) )
|
|
addAnchor( possible, SNAPPABLE | HORIZONTAL, aItem );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
EE_GRID_HELPER::ANCHOR* EE_GRID_HELPER::nearestAnchor( const VECTOR2I& aPos, int aFlags,
|
|
GRID_HELPER_GRIDS aGrid )
|
|
{
|
|
double minDist = std::numeric_limits<double>::max();
|
|
ANCHOR* best = nullptr;
|
|
|
|
for( ANCHOR& a : m_anchors )
|
|
{
|
|
SCH_ITEM* item = static_cast<SCH_ITEM*>( a.item );
|
|
|
|
if( ( aFlags & a.flags ) != aFlags )
|
|
continue;
|
|
|
|
if( aGrid == GRID_CONNECTABLE && !item->IsConnectable() )
|
|
continue;
|
|
else if( aGrid == GRID_GRAPHICS && item->IsConnectable() )
|
|
continue;
|
|
|
|
double dist = a.Distance( aPos );
|
|
|
|
if( dist < minDist )
|
|
{
|
|
minDist = dist;
|
|
best = &a;
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|