1341 lines
44 KiB
C++
1341 lines
44 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2019 CERN
|
|
* Copyright (C) 2019-2023 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 <sch_line_wire_bus_tool.h>
|
|
|
|
#include <wx/debug.h>
|
|
#include <wx/gdicmn.h>
|
|
#include <wx/string.h>
|
|
#include <wx/translation.h>
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <layer_ids.h>
|
|
#include <math/vector2d.h>
|
|
#include <advanced_config.h>
|
|
#include <gal/graphics_abstraction_layer.h>
|
|
#include <view/view_controls.h>
|
|
#include <tool/actions.h>
|
|
#include <tool/conditional_menu.h>
|
|
#include <tool/selection.h>
|
|
#include <tool/selection_conditions.h>
|
|
#include <tool/tool_event.h>
|
|
#include <trigo.h>
|
|
#include <eeschema_id.h>
|
|
#include <sch_bus_entry.h>
|
|
#include <sch_connection.h>
|
|
#include <sch_edit_frame.h>
|
|
#include <sch_line.h>
|
|
#include <sch_screen.h>
|
|
#include <sch_sheet.h>
|
|
#include <sch_sheet_pin.h>
|
|
#include <schematic.h>
|
|
#include <sch_commit.h>
|
|
#include <ee_actions.h>
|
|
#include <ee_grid_helper.h>
|
|
#include <ee_selection.h>
|
|
#include <ee_selection_tool.h>
|
|
|
|
|
|
using BUS_GETTER = std::function<SCH_LINE*()>;
|
|
|
|
class BUS_UNFOLD_MENU : public ACTION_MENU
|
|
{
|
|
public:
|
|
/**
|
|
* @param aBusGetter Function to get the bus to unfold, which will probably
|
|
* be looking for a likely bus in a selection.
|
|
*/
|
|
BUS_UNFOLD_MENU( BUS_GETTER aBusGetter ) :
|
|
ACTION_MENU( true ),
|
|
m_showTitle( false ),
|
|
m_busGetter( aBusGetter )
|
|
{
|
|
SetIcon( BITMAPS::add_line2bus );
|
|
SetTitle( _( "Unfold from Bus" ) );
|
|
}
|
|
|
|
void SetShowTitle()
|
|
{
|
|
m_showTitle = true;
|
|
}
|
|
|
|
bool PassHelpTextToHandler() override { return true; }
|
|
|
|
protected:
|
|
ACTION_MENU* create() const override
|
|
{
|
|
return new BUS_UNFOLD_MENU( m_busGetter );
|
|
}
|
|
|
|
private:
|
|
void update() override
|
|
{
|
|
SCH_LINE* bus = m_busGetter();
|
|
Clear();
|
|
// Pick up the pointer again because it may have been changed by SchematicCleanUp
|
|
bus = m_busGetter();
|
|
|
|
if( !bus )
|
|
{
|
|
Append( ID_POPUP_SCH_UNFOLD_BUS, _( "No bus selected" ), wxEmptyString );
|
|
Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
|
|
return;
|
|
}
|
|
|
|
SCH_CONNECTION* connection = bus->Connection();
|
|
|
|
if( !connection || !connection->IsBus() || connection->Members().empty() )
|
|
{
|
|
Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Bus has no members" ), wxEmptyString );
|
|
Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
|
|
return;
|
|
}
|
|
|
|
int idx = 0;
|
|
|
|
if( m_showTitle )
|
|
{
|
|
Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Unfold from Bus" ), wxEmptyString );
|
|
Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
|
|
}
|
|
|
|
for( const std::shared_ptr<SCH_CONNECTION>& member : connection->Members() )
|
|
{
|
|
int id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ );
|
|
wxString name = member->FullLocalName();
|
|
|
|
if( member->Type() == CONNECTION_TYPE::BUS )
|
|
{
|
|
ACTION_MENU* submenu = new ACTION_MENU( true, m_tool );
|
|
AppendSubMenu( submenu, SCH_CONNECTION::PrintBusForUI( name ), name );
|
|
|
|
for( const std::shared_ptr<SCH_CONNECTION>& sub_member : member->Members() )
|
|
{
|
|
id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ );
|
|
name = sub_member->FullLocalName();
|
|
submenu->Append( id, SCH_CONNECTION::PrintBusForUI( name ), name );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Append( id, name, wxEmptyString );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool m_showTitle;
|
|
BUS_GETTER m_busGetter;
|
|
};
|
|
|
|
|
|
/**
|
|
* Settings for bus unfolding that are persistent across invocations of the tool
|
|
*/
|
|
struct BUS_UNFOLD_PERSISTENT_SETTINGS
|
|
{
|
|
SPIN_STYLE label_spin_style;
|
|
};
|
|
|
|
|
|
static BUS_UNFOLD_PERSISTENT_SETTINGS busUnfoldPersistentSettings = {
|
|
SPIN_STYLE::RIGHT,
|
|
};
|
|
|
|
|
|
SCH_LINE_WIRE_BUS_TOOL::SCH_LINE_WIRE_BUS_TOOL() :
|
|
EE_TOOL_BASE<SCH_EDIT_FRAME>( "eeschema.InteractiveDrawingLineWireBus" ),
|
|
m_inDrawingTool( false )
|
|
{
|
|
m_busUnfold = {};
|
|
m_wires.reserve( 16 );
|
|
}
|
|
|
|
|
|
SCH_LINE_WIRE_BUS_TOOL::~SCH_LINE_WIRE_BUS_TOOL()
|
|
{
|
|
}
|
|
|
|
|
|
bool SCH_LINE_WIRE_BUS_TOOL::Init()
|
|
{
|
|
EE_TOOL_BASE::Init();
|
|
|
|
const auto busGetter = [this]()
|
|
{
|
|
return getBusForUnfolding();
|
|
};
|
|
|
|
std::shared_ptr<BUS_UNFOLD_MENU>
|
|
busUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>( busGetter );
|
|
busUnfoldMenu->SetTool( this );
|
|
m_menu.RegisterSubMenu( busUnfoldMenu );
|
|
|
|
std::shared_ptr<BUS_UNFOLD_MENU> selBusUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>( busGetter );
|
|
selBusUnfoldMenu->SetTool( m_selectionTool );
|
|
m_selectionTool->GetToolMenu().RegisterSubMenu( selBusUnfoldMenu );
|
|
|
|
auto wireOrBusTool =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return ( m_frame->IsCurrentTool( EE_ACTIONS::drawWire )
|
|
|| m_frame->IsCurrentTool( EE_ACTIONS::drawBus ) );
|
|
};
|
|
|
|
auto lineTool =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return m_frame->IsCurrentTool( EE_ACTIONS::drawLines );
|
|
};
|
|
|
|
auto belowRootSheetCondition =
|
|
[&]( const SELECTION& aSel )
|
|
{
|
|
return m_frame->GetCurrentSheet().Last() != &m_frame->Schematic().Root();
|
|
};
|
|
|
|
auto busSelection = EE_CONDITIONS::MoreThan( 0 )
|
|
&& EE_CONDITIONS::OnlyTypes( { SCH_ITEM_LOCATE_BUS_T } );
|
|
|
|
auto haveHighlight =
|
|
[&]( const SELECTION& sel )
|
|
{
|
|
SCH_EDIT_FRAME* editFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
|
|
|
|
return editFrame && !editFrame->GetHighlightedConnection().IsEmpty();
|
|
};
|
|
|
|
auto& ctxMenu = m_menu.GetMenu();
|
|
|
|
// Build the tool menu
|
|
//
|
|
ctxMenu.AddItem( EE_ACTIONS::clearHighlight, haveHighlight && EE_CONDITIONS::Idle, 1 );
|
|
ctxMenu.AddSeparator( haveHighlight && EE_CONDITIONS::Idle, 1 );
|
|
|
|
ctxMenu.AddSeparator( 10 );
|
|
ctxMenu.AddItem( EE_ACTIONS::drawWire, wireOrBusTool && EE_CONDITIONS::Idle, 10 );
|
|
ctxMenu.AddItem( EE_ACTIONS::drawBus, wireOrBusTool && EE_CONDITIONS::Idle, 10 );
|
|
ctxMenu.AddItem( EE_ACTIONS::drawLines, lineTool && EE_CONDITIONS::Idle, 10 );
|
|
|
|
ctxMenu.AddItem( EE_ACTIONS::undoLastSegment, EE_CONDITIONS::ShowAlways, 10 );
|
|
ctxMenu.AddItem( EE_ACTIONS::switchSegmentPosture, EE_CONDITIONS::ShowAlways, 10 );
|
|
ctxMenu.AddItem( ACTIONS::finishInteractive, IsDrawingLineWireOrBus, 10 );
|
|
|
|
ctxMenu.AddMenu( busUnfoldMenu.get(), EE_CONDITIONS::Idle, 10 );
|
|
|
|
ctxMenu.AddSeparator( 100 );
|
|
ctxMenu.AddItem( EE_ACTIONS::placeJunction, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
|
|
ctxMenu.AddItem( EE_ACTIONS::placeLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
|
|
ctxMenu.AddItem( EE_ACTIONS::placeClassLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
|
|
ctxMenu.AddItem( EE_ACTIONS::placeGlobalLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
|
|
ctxMenu.AddItem( EE_ACTIONS::placeHierLabel, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
|
|
ctxMenu.AddItem( EE_ACTIONS::breakWire, wireOrBusTool && EE_CONDITIONS::Idle, 100 );
|
|
ctxMenu.AddItem( EE_ACTIONS::slice, ( wireOrBusTool || lineTool )
|
|
&& EE_CONDITIONS::Idle, 100 );
|
|
ctxMenu.AddItem( EE_ACTIONS::leaveSheet, belowRootSheetCondition, 150 );
|
|
|
|
ctxMenu.AddSeparator( 200 );
|
|
ctxMenu.AddItem( EE_ACTIONS::selectNode, wireOrBusTool && EE_CONDITIONS::Idle, 200 );
|
|
ctxMenu.AddItem( EE_ACTIONS::selectConnection, wireOrBusTool && EE_CONDITIONS::Idle, 200 );
|
|
|
|
// Add bus unfolding to the selection tool
|
|
//
|
|
CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
|
|
|
|
selToolMenu.AddMenu( selBusUnfoldMenu.get(), busSelection && EE_CONDITIONS::Idle, 100 );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SCH_LINE_WIRE_BUS_TOOL::IsDrawingLineWireOrBus( const SELECTION& aSelection )
|
|
{
|
|
// NOTE: for immediate hotkeys, it is NOT required that the line, wire or bus tool
|
|
// be selected
|
|
SCH_ITEM* item = (SCH_ITEM*) aSelection.Front();
|
|
return item && item->IsNew() && item->Type() == SCH_LINE_T;
|
|
}
|
|
|
|
|
|
int SCH_LINE_WIRE_BUS_TOOL::DrawSegments( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_inDrawingTool )
|
|
return 0;
|
|
|
|
REENTRANCY_GUARD guard( &m_inDrawingTool );
|
|
|
|
const DRAW_SEGMENT_EVENT_PARAMS* params = aEvent.Parameter<const DRAW_SEGMENT_EVENT_PARAMS*>();
|
|
|
|
m_frame->PushTool( aEvent );
|
|
m_toolMgr->RunAction( EE_ACTIONS::clearSelection );
|
|
|
|
if( aEvent.HasPosition() )
|
|
{
|
|
EE_GRID_HELPER grid( m_toolMgr );
|
|
GRID_HELPER_GRIDS gridType = ( params->layer == LAYER_NOTES ) ? GRID_GRAPHICS : GRID_WIRES;
|
|
|
|
grid.SetSnap( !aEvent.Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !aEvent.DisableGridSnapping() );
|
|
|
|
VECTOR2D cursorPos = grid.BestSnapAnchor( aEvent.Position(), gridType, nullptr );
|
|
startSegments( params->layer, cursorPos, params->sourceSegment );
|
|
}
|
|
|
|
return doDrawSegments( aEvent, params->layer, params->quitOnDraw );
|
|
}
|
|
|
|
|
|
int SCH_LINE_WIRE_BUS_TOOL::UnfoldBus( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( m_inDrawingTool )
|
|
return 0;
|
|
|
|
REENTRANCY_GUARD guard( &m_inDrawingTool );
|
|
|
|
wxString* netPtr = aEvent.Parameter<wxString*>();
|
|
wxString net;
|
|
SCH_LINE* segment = nullptr;
|
|
|
|
m_frame->PushTool( aEvent );
|
|
Activate();
|
|
|
|
if( netPtr )
|
|
{
|
|
net = *netPtr;
|
|
delete netPtr;
|
|
}
|
|
else
|
|
{
|
|
const auto busGetter = [this]()
|
|
{
|
|
return getBusForUnfolding();
|
|
};
|
|
BUS_UNFOLD_MENU unfoldMenu( busGetter );
|
|
unfoldMenu.SetTool( this );
|
|
unfoldMenu.SetShowTitle();
|
|
|
|
SetContextMenu( &unfoldMenu, CMENU_NOW );
|
|
|
|
while( TOOL_EVENT* evt = Wait() )
|
|
{
|
|
if( evt->Action() == TA_CHOICE_MENU_CHOICE )
|
|
{
|
|
std::optional<int> id = evt->GetCommandId();
|
|
|
|
if( id && ( *id > 0 ) )
|
|
net = *evt->Parameter<wxString*>();
|
|
|
|
break;
|
|
}
|
|
else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Break a wire for the given net out of the bus
|
|
if( !net.IsEmpty() )
|
|
segment = doUnfoldBus( net );
|
|
|
|
// If we have an unfolded wire to draw, then draw it
|
|
if( segment )
|
|
{
|
|
return doDrawSegments( aEvent, LAYER_WIRE, false );
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aEvent );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
SCH_LINE* SCH_LINE_WIRE_BUS_TOOL::getBusForUnfolding()
|
|
{
|
|
EE_SELECTION& selection = m_selectionTool->RequestSelection( { SCH_ITEM_LOCATE_BUS_T } );
|
|
return static_cast<SCH_LINE*>( selection.Front() );
|
|
}
|
|
|
|
|
|
SCH_LINE* SCH_LINE_WIRE_BUS_TOOL::doUnfoldBus( const wxString& aNet,
|
|
const std::optional<VECTOR2I>& aPos )
|
|
{
|
|
SCHEMATIC_SETTINGS& cfg = getModel<SCHEMATIC>()->Settings();
|
|
SCH_SCREEN* screen = m_frame->GetScreen();
|
|
// use the same function as the menu selector, so we choose the same bus segment
|
|
SCH_LINE* const bus = getBusForUnfolding();
|
|
|
|
if ( bus == nullptr )
|
|
{
|
|
wxASSERT_MSG( false,
|
|
wxString::Format( "Couldn't find the originating bus line (but had a net: %s )",
|
|
aNet ) );
|
|
return nullptr;
|
|
}
|
|
|
|
VECTOR2I pos = aPos.value_or( static_cast<VECTOR2I>( getViewControls()->GetCursorPosition() ) );
|
|
|
|
// It is possible for the position to be near the bus, but not exactly on it, but
|
|
// we need the bus entry to be on the bus exactly to connect.
|
|
// If the bus segment is H or V, this will be on the selection grid, if it's not,
|
|
// it might not be, but it won't be a broken connection (and the user asked for it!)
|
|
pos = bus->GetSeg().NearestPoint( pos );
|
|
|
|
m_toolMgr->RunAction( EE_ACTIONS::clearSelection );
|
|
|
|
m_busUnfold.entry = new SCH_BUS_WIRE_ENTRY( pos );
|
|
m_busUnfold.entry->SetParent( screen );
|
|
m_frame->AddToScreen( m_busUnfold.entry, m_frame->GetScreen() );
|
|
|
|
m_busUnfold.label = new SCH_LABEL( m_busUnfold.entry->GetEnd(), aNet );
|
|
m_busUnfold.label->SetTextSize( VECTOR2I( cfg.m_DefaultTextSize, cfg.m_DefaultTextSize ) );
|
|
m_busUnfold.label->SetSpinStyle( busUnfoldPersistentSettings.label_spin_style );
|
|
m_busUnfold.label->SetParent( m_frame->GetScreen() );
|
|
m_busUnfold.label->SetFlags( IS_NEW | IS_MOVING );
|
|
|
|
m_busUnfold.in_progress = true;
|
|
m_busUnfold.origin = pos;
|
|
m_busUnfold.net_name = aNet;
|
|
|
|
getViewControls()->SetCrossHairCursorPosition( m_busUnfold.entry->GetEnd(), false );
|
|
|
|
std::vector<DANGLING_END_ITEM> endPointsByType;
|
|
|
|
for( SCH_ITEM* item : screen->Items().Overlapping( m_busUnfold.entry->GetBoundingBox() ) )
|
|
item->GetEndPoints( endPointsByType );
|
|
|
|
std::vector<DANGLING_END_ITEM> endPointsByPos = endPointsByType;
|
|
DANGLING_END_ITEM_HELPER::sort_dangling_end_items( endPointsByType, endPointsByPos );
|
|
m_busUnfold.entry->UpdateDanglingState( endPointsByType, endPointsByPos );
|
|
m_busUnfold.entry->SetEndDangling( false );
|
|
m_busUnfold.label->SetIsDangling( false );
|
|
|
|
return startSegments( LAYER_WIRE, m_busUnfold.entry->GetEnd() );
|
|
}
|
|
|
|
|
|
const SCH_SHEET_PIN* SCH_LINE_WIRE_BUS_TOOL::getSheetPin( const VECTOR2I& aPosition )
|
|
{
|
|
SCH_SCREEN* screen = m_frame->GetScreen();
|
|
|
|
for( SCH_ITEM* item : screen->Items().Overlapping( SCH_SHEET_T, aPosition ) )
|
|
{
|
|
SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
|
|
|
|
for( SCH_SHEET_PIN* pin : sheet->GetPins() )
|
|
{
|
|
if( pin->GetPosition() == aPosition )
|
|
return pin;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void SCH_LINE_WIRE_BUS_TOOL::computeBreakPoint( const std::pair<SCH_LINE*, SCH_LINE*>& aSegments,
|
|
VECTOR2I& aPosition,
|
|
LINE_MODE mode,
|
|
bool posture )
|
|
{
|
|
wxCHECK_RET( aSegments.first && aSegments.second,
|
|
wxT( "Cannot compute break point of NULL line segment." ) );
|
|
|
|
VECTOR2I midPoint;
|
|
SCH_LINE* segment = aSegments.first;
|
|
SCH_LINE* nextSegment = aSegments.second;
|
|
|
|
VECTOR2I delta = aPosition - segment->GetStartPoint();
|
|
int xDir = delta.x > 0 ? 1 : -1;
|
|
int yDir = delta.y > 0 ? 1 : -1;
|
|
|
|
|
|
bool preferHorizontal;
|
|
bool preferVertical;
|
|
|
|
if( ( mode == LINE_MODE_45 ) && posture )
|
|
{
|
|
preferHorizontal = ( nextSegment->GetEndPoint().x - nextSegment->GetStartPoint().x ) != 0;
|
|
preferVertical = ( nextSegment->GetEndPoint().y - nextSegment->GetStartPoint().y ) != 0;
|
|
}
|
|
else
|
|
{
|
|
preferHorizontal = ( segment->GetEndPoint().x - segment->GetStartPoint().x ) != 0;
|
|
preferVertical = ( segment->GetEndPoint().y - segment->GetStartPoint().y ) != 0;
|
|
}
|
|
|
|
// Check for times we need to force horizontal sheet pin connections
|
|
const SCH_SHEET_PIN* connectedPin = getSheetPin( segment->GetStartPoint() );
|
|
SHEET_SIDE force = connectedPin ? connectedPin->GetSide() : SHEET_SIDE::UNDEFINED;
|
|
|
|
if( force == SHEET_SIDE::LEFT || force == SHEET_SIDE::RIGHT )
|
|
{
|
|
if( aPosition.x == connectedPin->GetPosition().x ) // push outside sheet boundary
|
|
{
|
|
int direction = ( force == SHEET_SIDE::LEFT ) ? -1 : 1;
|
|
aPosition.x += KiROUND( getView()->GetGAL()->GetGridSize().x * direction );
|
|
}
|
|
|
|
preferHorizontal = true;
|
|
preferVertical = false;
|
|
}
|
|
|
|
|
|
auto breakVertical = [&]() mutable
|
|
{
|
|
switch( mode )
|
|
{
|
|
case LINE_MODE_45:
|
|
if( !posture )
|
|
{
|
|
midPoint.x = segment->GetStartPoint().x;
|
|
midPoint.y = aPosition.y - yDir * abs( delta.x );
|
|
}
|
|
else
|
|
{
|
|
midPoint.x = aPosition.x;
|
|
midPoint.y = segment->GetStartPoint().y + yDir * abs( delta.x );
|
|
}
|
|
break;
|
|
default:
|
|
midPoint.x = segment->GetStartPoint().x;
|
|
midPoint.y = aPosition.y;
|
|
}
|
|
};
|
|
|
|
|
|
auto breakHorizontal = [&]() mutable
|
|
{
|
|
switch( mode )
|
|
{
|
|
case LINE_MODE_45:
|
|
if( !posture )
|
|
{
|
|
midPoint.x = aPosition.x - xDir * abs( delta.y );
|
|
midPoint.y = segment->GetStartPoint().y;
|
|
}
|
|
else
|
|
{
|
|
midPoint.x = segment->GetStartPoint().x + xDir * abs( delta.y );
|
|
midPoint.y = aPosition.y;
|
|
}
|
|
break;
|
|
default:
|
|
midPoint.x = aPosition.x;
|
|
midPoint.y = segment->GetStartPoint().y;
|
|
}
|
|
};
|
|
|
|
|
|
// Maintain current line shape if we can, e.g. if we were originally moving
|
|
// vertically keep the first segment vertical
|
|
if( preferVertical )
|
|
breakVertical();
|
|
else if( preferHorizontal )
|
|
breakHorizontal();
|
|
|
|
// Check if our 45 degree angle is one of these shapes
|
|
// /
|
|
// /
|
|
// /
|
|
// /__________
|
|
VECTOR2I deltaMidpoint = midPoint - segment->GetStartPoint();
|
|
|
|
if( mode == LINE_MODE::LINE_MODE_45 && !posture
|
|
&& ( ( alg::signbit( deltaMidpoint.x ) != alg::signbit( delta.x ) )
|
|
|| ( alg::signbit( deltaMidpoint.y ) != alg::signbit( delta.y ) ) ) )
|
|
{
|
|
preferVertical = false;
|
|
preferHorizontal = false;
|
|
}
|
|
else if( mode == LINE_MODE::LINE_MODE_45 && posture
|
|
&& ( ( abs( deltaMidpoint.x ) > abs( delta.x ) )
|
|
|| ( abs( deltaMidpoint.y ) > abs( delta.y ) ) ) )
|
|
{
|
|
preferVertical = false;
|
|
preferHorizontal = false;
|
|
}
|
|
|
|
if( !preferHorizontal && !preferVertical )
|
|
{
|
|
if( std::abs( delta.x ) < std::abs( delta.y ) )
|
|
breakVertical();
|
|
else
|
|
breakHorizontal();
|
|
}
|
|
|
|
segment->SetEndPoint( midPoint );
|
|
nextSegment->SetStartPoint( midPoint );
|
|
nextSegment->SetEndPoint( aPosition );
|
|
}
|
|
|
|
|
|
int SCH_LINE_WIRE_BUS_TOOL::doDrawSegments( const TOOL_EVENT& aTool, int aType, bool aQuitOnDraw )
|
|
{
|
|
SCH_SCREEN* screen = m_frame->GetScreen();
|
|
SCH_LINE* segment = nullptr;
|
|
EE_GRID_HELPER grid( m_toolMgr );
|
|
GRID_HELPER_GRIDS gridType = ( aType == LAYER_NOTES ) ? GRID_GRAPHICS : GRID_WIRES;
|
|
KIGFX::VIEW_CONTROLS* controls = getViewControls();
|
|
int lastMode = m_frame->eeconfig()->m_Drawing.line_mode;
|
|
static bool posture = false;
|
|
|
|
auto setCursor =
|
|
[&]()
|
|
{
|
|
if( aType == LAYER_WIRE )
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_WIRE );
|
|
else if( aType == LAYER_BUS )
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_BUS );
|
|
else if( aType == LAYER_NOTES )
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_GRAPHIC );
|
|
else
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_WIRE );
|
|
};
|
|
|
|
auto cleanup =
|
|
[&] ()
|
|
{
|
|
m_toolMgr->RunAction( EE_ACTIONS::clearSelection );
|
|
|
|
for( SCH_LINE* wire : m_wires )
|
|
delete wire;
|
|
|
|
m_wires.clear();
|
|
segment = nullptr;
|
|
|
|
if( m_busUnfold.entry )
|
|
m_frame->RemoveFromScreen( m_busUnfold.entry, screen );
|
|
|
|
if( m_busUnfold.label && !m_busUnfold.label_placed )
|
|
m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true );
|
|
|
|
if( m_busUnfold.label && m_busUnfold.label_placed )
|
|
m_frame->RemoveFromScreen( m_busUnfold.label, screen );
|
|
|
|
delete m_busUnfold.entry;
|
|
delete m_busUnfold.label;
|
|
m_busUnfold = {};
|
|
|
|
m_view->ClearPreview();
|
|
m_view->ShowPreview( false );
|
|
};
|
|
|
|
Activate();
|
|
// Must be done after Activate() so that it gets set into the correct context
|
|
controls->ShowCursor( true );
|
|
// Set initial cursor
|
|
setCursor();
|
|
|
|
// Add the new label to the selection so the rotate command operates on it
|
|
if( m_busUnfold.label )
|
|
m_selectionTool->AddItemToSel( m_busUnfold.label, true );
|
|
|
|
// Continue the existing wires if we've started (usually by immediate action preference)
|
|
if( !m_wires.empty() )
|
|
segment = m_wires.back();
|
|
|
|
VECTOR2I contextMenuPos;
|
|
|
|
// Main loop: keep receiving events
|
|
while( TOOL_EVENT* evt = Wait() )
|
|
{
|
|
LINE_MODE currentMode = (LINE_MODE) m_frame->eeconfig()->m_Drawing.line_mode;
|
|
bool twoSegments = currentMode != LINE_MODE::LINE_MODE_FREE;
|
|
|
|
// The tool hotkey is interpreted as a click when drawing
|
|
bool isSyntheticClick = ( segment || m_busUnfold.in_progress ) && evt->IsActivate()
|
|
&& evt->HasPosition() && evt->Matches( aTool );
|
|
|
|
setCursor();
|
|
grid.SetMask( GRID_HELPER::ALL );
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
|
|
|
|
if( segment )
|
|
{
|
|
if( segment->GetStartPoint().x == segment->GetEndPoint().x )
|
|
grid.ClearMaskFlag( GRID_HELPER::VERTICAL );
|
|
|
|
if( segment->GetStartPoint().y == segment->GetEndPoint().y )
|
|
grid.ClearMaskFlag( GRID_HELPER::HORIZONTAL );
|
|
}
|
|
|
|
VECTOR2D eventPosition = evt->HasPosition() ? evt->Position()
|
|
: controls->GetMousePosition();
|
|
|
|
VECTOR2I cursorPos = grid.BestSnapAnchor( eventPosition, gridType, segment );
|
|
controls->ForceCursorPosition( true, cursorPos );
|
|
|
|
// Need to handle change in H/V mode while drawing
|
|
if( currentMode != lastMode )
|
|
{
|
|
// Need to delete extra segment if we have one
|
|
if( segment && currentMode == LINE_MODE::LINE_MODE_FREE && m_wires.size() >= 2 )
|
|
{
|
|
m_wires.pop_back();
|
|
m_selectionTool->RemoveItemFromSel( segment );
|
|
delete segment;
|
|
|
|
segment = m_wires.back();
|
|
segment->SetEndPoint( cursorPos );
|
|
}
|
|
// Add a segment so we can move orthogonally/45
|
|
else if( segment && lastMode == LINE_MODE::LINE_MODE_FREE )
|
|
{
|
|
segment->SetEndPoint( cursorPos );
|
|
|
|
// Create a new segment, and chain it after the current segment.
|
|
segment = static_cast<SCH_LINE*>( segment->Duplicate() );
|
|
segment->SetFlags( IS_NEW | IS_MOVING );
|
|
segment->SetStartPoint( cursorPos );
|
|
m_wires.push_back( segment );
|
|
|
|
m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ );
|
|
}
|
|
|
|
lastMode = currentMode;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Handle cancel:
|
|
//
|
|
if( evt->IsCancelInteractive() )
|
|
{
|
|
m_frame->GetInfoBar()->Dismiss();
|
|
|
|
if( segment || m_busUnfold.in_progress )
|
|
{
|
|
cleanup();
|
|
|
|
if( aQuitOnDraw )
|
|
{
|
|
m_frame->PopTool( aTool );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aTool );
|
|
break;
|
|
}
|
|
}
|
|
else if( evt->IsActivate() && !isSyntheticClick )
|
|
{
|
|
if( segment || m_busUnfold.in_progress )
|
|
{
|
|
m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel drawing." ) );
|
|
evt->SetPassEvent( false );
|
|
continue;
|
|
}
|
|
|
|
if( evt->IsMoveTool() )
|
|
{
|
|
// leave ourselves on the stack so we come back after the move
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
m_frame->PopTool( aTool );
|
|
break;
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------
|
|
// Handle finish:
|
|
//
|
|
else if( evt->IsAction( &ACTIONS::finishInteractive ) )
|
|
{
|
|
if( segment || m_busUnfold.in_progress )
|
|
{
|
|
finishSegments();
|
|
segment = nullptr;
|
|
|
|
if( aQuitOnDraw )
|
|
{
|
|
m_frame->PopTool( aTool );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------
|
|
// Handle click:
|
|
//
|
|
else if( evt->IsClick( BUT_LEFT )
|
|
|| ( segment && evt->IsDblClick( BUT_LEFT ) )
|
|
|| isSyntheticClick )
|
|
{
|
|
// First click when unfolding places the label and wire-to-bus entry
|
|
if( m_busUnfold.in_progress && !m_busUnfold.label_placed )
|
|
{
|
|
wxASSERT( aType == LAYER_WIRE );
|
|
|
|
m_frame->AddToScreen( m_busUnfold.label, screen );
|
|
m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true );
|
|
m_busUnfold.label_placed = true;
|
|
}
|
|
|
|
if( !segment )
|
|
{
|
|
segment = startSegments( aType, VECTOR2D( cursorPos ) );
|
|
}
|
|
// Create a new segment if we're out of previously-created ones
|
|
else if( !segment->IsNull()
|
|
|| ( twoSegments && !m_wires[m_wires.size() - 2]->IsNull() ) )
|
|
{
|
|
// Terminate the command if the end point is on a pin, junction, label, or another
|
|
// wire or bus.
|
|
if( screen->IsTerminalPoint( cursorPos, segment->GetLayer() ) )
|
|
{
|
|
finishSegments();
|
|
segment = nullptr;
|
|
|
|
if( aQuitOnDraw )
|
|
{
|
|
m_frame->PopTool( aTool );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int placedSegments = 1;
|
|
|
|
// When placing lines with the forty-five degree end, the user is
|
|
// targetting the endpoint with the angled portion, so it's more
|
|
// intuitive to place both segments at the same time.
|
|
if( currentMode == LINE_MODE::LINE_MODE_45 )
|
|
placedSegments++;
|
|
|
|
segment->SetEndPoint( cursorPos );
|
|
|
|
for( int i = 0; i < placedSegments; i++ )
|
|
{
|
|
// Create a new segment, and chain it after the current segment.
|
|
segment = static_cast<SCH_LINE*>( segment->Duplicate() );
|
|
segment->SetFlags( IS_NEW | IS_MOVING );
|
|
segment->SetStartPoint( cursorPos );
|
|
m_wires.push_back( segment );
|
|
|
|
m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( evt->IsDblClick( BUT_LEFT ) && segment )
|
|
{
|
|
if( twoSegments && m_wires.size() >= 2 )
|
|
{
|
|
computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos,
|
|
currentMode, posture );
|
|
}
|
|
|
|
finishSegments();
|
|
segment = nullptr;
|
|
|
|
if( aQuitOnDraw )
|
|
{
|
|
m_frame->PopTool( aTool );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------
|
|
// Handle motion:
|
|
//
|
|
else if( evt->IsMotion() || evt->IsAction( &ACTIONS::refreshPreview ) )
|
|
{
|
|
m_view->ClearPreview();
|
|
|
|
// Update the bus unfold posture based on the mouse movement
|
|
if( m_busUnfold.in_progress && !m_busUnfold.label_placed )
|
|
{
|
|
VECTOR2I cursor_delta = cursorPos - m_busUnfold.origin;
|
|
SCH_BUS_WIRE_ENTRY* entry = m_busUnfold.entry;
|
|
|
|
bool flipX = ( cursor_delta.x < 0 );
|
|
bool flipY = ( cursor_delta.y < 0 );
|
|
|
|
// Erase and redraw if necessary
|
|
if( flipX != m_busUnfold.flipX || flipY != m_busUnfold.flipY )
|
|
{
|
|
VECTOR2I size = entry->GetSize();
|
|
int ySign = flipY ? -1 : 1;
|
|
int xSign = flipX ? -1 : 1;
|
|
|
|
size.x = std::abs( size.x ) * xSign;
|
|
size.y = std::abs( size.y ) * ySign;
|
|
entry->SetSize( size );
|
|
|
|
m_busUnfold.flipY = flipY;
|
|
m_busUnfold.flipX = flipX;
|
|
|
|
m_frame->UpdateItem( entry, false, true );
|
|
m_wires.front()->SetStartPoint( entry->GetEnd() );
|
|
}
|
|
|
|
// Update the label "ghost" position
|
|
m_busUnfold.label->SetPosition( cursorPos );
|
|
m_view->AddToPreview( m_busUnfold.label->Clone() );
|
|
|
|
// Ensure segment is non-null at the start of bus unfold
|
|
if( !segment )
|
|
segment = m_wires.back();
|
|
}
|
|
|
|
if( segment )
|
|
{
|
|
// Coerce the line to vertical/horizontal/45 as necessary
|
|
if( twoSegments && m_wires.size() >= 2 )
|
|
{
|
|
computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos,
|
|
currentMode, posture );
|
|
}
|
|
else
|
|
{
|
|
segment->SetEndPoint( cursorPos );
|
|
}
|
|
}
|
|
|
|
for( SCH_LINE* wire : m_wires )
|
|
{
|
|
if( !wire->IsNull() )
|
|
m_view->AddToPreview( wire->Clone() );
|
|
}
|
|
}
|
|
else if( evt->IsAction( &EE_ACTIONS::undoLastSegment )
|
|
|| evt->IsAction( &ACTIONS::doDelete )
|
|
|| evt->IsAction( &ACTIONS::undo ) )
|
|
{
|
|
if( ( currentMode == LINE_MODE::LINE_MODE_FREE && m_wires.size() > 1 )
|
|
|| ( LINE_MODE::LINE_MODE_90 && m_wires.size() > 2 ) )
|
|
{
|
|
m_view->ClearPreview();
|
|
|
|
m_wires.pop_back();
|
|
m_selectionTool->RemoveItemFromSel( segment );
|
|
delete segment;
|
|
|
|
segment = m_wires.back();
|
|
cursorPos = segment->GetEndPoint();
|
|
getViewControls()->WarpMouseCursor( cursorPos, true );
|
|
|
|
// Find new bend point for current mode
|
|
if( twoSegments && m_wires.size() >= 2 )
|
|
{
|
|
computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos,
|
|
currentMode, posture );
|
|
}
|
|
else
|
|
{
|
|
segment->SetEndPoint( cursorPos );
|
|
}
|
|
|
|
for( SCH_LINE* wire : m_wires )
|
|
{
|
|
if( !wire->IsNull() )
|
|
m_view->AddToPreview( wire->Clone() );
|
|
}
|
|
}
|
|
else if( evt->IsAction( &ACTIONS::undo ) )
|
|
{
|
|
// Dispatch as normal undo event
|
|
evt->SetPassEvent();
|
|
}
|
|
else
|
|
{
|
|
wxBell();
|
|
}
|
|
}
|
|
else if( evt->IsAction( &EE_ACTIONS::switchSegmentPosture ) && m_wires.size() >= 2 )
|
|
{
|
|
posture = !posture;
|
|
|
|
// The 90 degree mode doesn't have a forced posture like
|
|
// the 45 degree mode and computeBreakPoint maintains existing 90s' postures.
|
|
// Instead, just swap the 90 angle here.
|
|
if( currentMode == LINE_MODE::LINE_MODE_90 )
|
|
{
|
|
m_view->ClearPreview();
|
|
|
|
SCH_LINE* line2 = m_wires[m_wires.size() - 1];
|
|
SCH_LINE* line1 = m_wires[m_wires.size() - 2];
|
|
|
|
VECTOR2I delta2 = line2->GetEndPoint() - line2->GetStartPoint();
|
|
VECTOR2I delta1 = line1->GetEndPoint() - line1->GetStartPoint();
|
|
|
|
line2->SetStartPoint(line2->GetEndPoint() - delta1);
|
|
line1->SetEndPoint(line1->GetStartPoint() + delta2);
|
|
|
|
for( SCH_LINE* wire : m_wires )
|
|
{
|
|
if( !wire->IsNull() )
|
|
m_view->AddToPreview( wire->Clone() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode,
|
|
posture );
|
|
|
|
m_toolMgr->PostAction( ACTIONS::refreshPreview );
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------
|
|
// Handle context menu:
|
|
//
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
// Warp after context menu only if dragging...
|
|
if( !segment )
|
|
m_toolMgr->VetoContextMenuMouseWarp();
|
|
|
|
contextMenuPos = cursorPos;
|
|
m_menu.ShowContextMenu( m_selectionTool->GetSelection() );
|
|
}
|
|
else if( evt->Category() == TC_COMMAND && evt->Action() == TA_CHOICE_MENU_CHOICE )
|
|
{
|
|
if( *evt->GetCommandId() >= ID_POPUP_SCH_UNFOLD_BUS
|
|
&& *evt->GetCommandId() <= ID_POPUP_SCH_UNFOLD_BUS_END )
|
|
{
|
|
wxASSERT_MSG( !segment, "Bus unfold event received when already drawing!" );
|
|
|
|
aType = LAYER_WIRE;
|
|
wxString net = *evt->Parameter<wxString*>();
|
|
segment = doUnfoldBus( net, contextMenuPos );
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------
|
|
// Handle TOOL_ACTION special cases
|
|
//
|
|
else if( evt->IsAction( &EE_ACTIONS::rotateCW ) || evt->IsAction( &EE_ACTIONS::rotateCCW ) )
|
|
{
|
|
if( m_busUnfold.in_progress )
|
|
{
|
|
m_busUnfold.label->Rotate90( evt->IsAction( &EE_ACTIONS::rotateCW ) );
|
|
busUnfoldPersistentSettings.label_spin_style = m_busUnfold.label->GetSpinStyle();
|
|
|
|
m_toolMgr->PostAction( ACTIONS::refreshPreview );
|
|
}
|
|
else
|
|
{
|
|
wxBell();
|
|
}
|
|
}
|
|
else if( evt->IsAction( &ACTIONS::redo ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
else
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
|
|
// Enable autopanning and cursor capture only when there is a segment to be placed
|
|
controls->SetAutoPan( segment != nullptr );
|
|
controls->CaptureCursor( segment != nullptr );
|
|
}
|
|
|
|
controls->SetAutoPan( false );
|
|
controls->CaptureCursor( false );
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
|
|
controls->ForceCursorPosition( false );
|
|
return 0;
|
|
}
|
|
|
|
|
|
SCH_LINE* SCH_LINE_WIRE_BUS_TOOL::startSegments( int aType, const VECTOR2D& aPos,
|
|
SCH_LINE* aSegment )
|
|
{
|
|
if( !aSegment )
|
|
aSegment = m_frame->GetScreen()->GetLine( aPos, 0, aType );
|
|
|
|
if( !aSegment )
|
|
{
|
|
switch( aType )
|
|
{
|
|
default: aSegment = new SCH_LINE( aPos, LAYER_NOTES ); break;
|
|
case LAYER_WIRE: aSegment = new SCH_LINE( aPos, LAYER_WIRE ); break;
|
|
case LAYER_BUS: aSegment = new SCH_LINE( aPos, LAYER_BUS ); break;
|
|
}
|
|
|
|
// Give segments a parent so they find the default line/wire/bus widths
|
|
aSegment->SetParent( &m_frame->Schematic() );
|
|
}
|
|
else
|
|
{
|
|
aSegment = static_cast<SCH_LINE*>( aSegment->Duplicate() );
|
|
aSegment->SetStartPoint( aPos );
|
|
}
|
|
|
|
|
|
aSegment->SetFlags( IS_NEW | IS_MOVING );
|
|
m_wires.push_back( aSegment );
|
|
|
|
m_selectionTool->AddItemToSel( aSegment, true /*quiet mode*/ );
|
|
|
|
// We need 2 segments to go from a given start pin to an end point when the
|
|
// horizontal and vertical lines only switch is on.
|
|
if( m_frame->eeconfig()->m_Drawing.line_mode )
|
|
{
|
|
aSegment = static_cast<SCH_LINE*>( aSegment->Duplicate() );
|
|
aSegment->SetFlags( IS_NEW | IS_MOVING );
|
|
m_wires.push_back( aSegment );
|
|
|
|
m_selectionTool->AddItemToSel( aSegment, true /*quiet mode*/ );
|
|
}
|
|
|
|
return aSegment;
|
|
}
|
|
|
|
|
|
/**
|
|
* In a contiguous list of wires, remove wires that backtrack over the previous
|
|
* wire. Example:
|
|
*
|
|
* Wire is added:
|
|
* ---------------------------------------->
|
|
*
|
|
* A second wire backtracks over it:
|
|
* -------------------<====================>
|
|
*
|
|
* simplifyWireList is called:
|
|
* ------------------->
|
|
*/
|
|
void SCH_LINE_WIRE_BUS_TOOL::simplifyWireList()
|
|
{
|
|
for( auto it = m_wires.begin(); it != m_wires.end(); )
|
|
{
|
|
SCH_LINE* line = *it;
|
|
|
|
if( line->IsNull() )
|
|
{
|
|
delete line;
|
|
it = m_wires.erase( it );
|
|
continue;
|
|
}
|
|
|
|
auto next_it = it;
|
|
++next_it;
|
|
|
|
if( next_it == m_wires.end() )
|
|
break;
|
|
|
|
SCH_LINE* next_line = *next_it;
|
|
|
|
if( SCH_LINE* merged = line->MergeOverlap( m_frame->GetScreen(), next_line, false ) )
|
|
{
|
|
delete line;
|
|
delete next_line;
|
|
it = m_wires.erase( it );
|
|
*it = merged;
|
|
}
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
|
|
void SCH_LINE_WIRE_BUS_TOOL::finishSegments()
|
|
{
|
|
// Clear selection when done so that a new wire can be started.
|
|
// NOTE: this must be done before simplifyWireList is called or we might end up with
|
|
// freed selected items.
|
|
m_toolMgr->RunAction( EE_ACTIONS::clearSelection );
|
|
|
|
SCH_SCREEN* screen = m_frame->GetScreen();
|
|
SCH_COMMIT commit( m_toolMgr );
|
|
|
|
// Remove segments backtracking over others
|
|
simplifyWireList();
|
|
|
|
// Collect the possible connection points for the new lines
|
|
std::vector<VECTOR2I> connections = screen->GetConnections();
|
|
std::vector<VECTOR2I> new_ends;
|
|
|
|
// Check each new segment for possible junctions and add/split if needed
|
|
for( SCH_LINE* wire : m_wires )
|
|
{
|
|
if( wire->HasFlag( SKIP_STRUCT ) )
|
|
continue;
|
|
|
|
std::vector<VECTOR2I> tmpends = wire->GetConnectionPoints();
|
|
|
|
new_ends.insert( new_ends.end(), tmpends.begin(), tmpends.end() );
|
|
|
|
for( const VECTOR2I& pt : connections )
|
|
{
|
|
if( IsPointOnSegment( wire->GetStartPoint(), wire->GetEndPoint(), pt ) )
|
|
new_ends.push_back( pt );
|
|
}
|
|
|
|
commit.Added( wire, screen );
|
|
}
|
|
|
|
if( m_busUnfold.in_progress && m_busUnfold.label_placed )
|
|
{
|
|
wxASSERT( m_busUnfold.entry && m_busUnfold.label );
|
|
|
|
commit.Added( m_busUnfold.entry, screen );
|
|
m_frame->SaveCopyForRepeatItem( m_busUnfold.entry );
|
|
|
|
commit.Added( m_busUnfold.label, screen );
|
|
m_frame->AddCopyForRepeatItem( m_busUnfold.label );
|
|
m_busUnfold.label->ClearEditFlags();
|
|
}
|
|
else if( !m_wires.empty() )
|
|
{
|
|
m_frame->SaveCopyForRepeatItem( m_wires[0] );
|
|
}
|
|
|
|
for( size_t ii = 1; ii < m_wires.size(); ++ii )
|
|
m_frame->AddCopyForRepeatItem( m_wires[ii] );
|
|
|
|
// Get the last non-null wire (this is the last created segment).
|
|
if( !m_wires.empty() )
|
|
m_frame->AddCopyForRepeatItem( m_wires.back() );
|
|
|
|
// Add the new wires
|
|
for( SCH_LINE* wire : m_wires )
|
|
{
|
|
wire->ClearFlags( IS_NEW | IS_MOVING );
|
|
m_frame->AddToScreen( wire, screen );
|
|
}
|
|
|
|
m_wires.clear();
|
|
m_view->ClearPreview();
|
|
m_view->ShowPreview( false );
|
|
|
|
getViewControls()->CaptureCursor( false );
|
|
getViewControls()->SetAutoPan( false );
|
|
|
|
// Correct and remove segments that need to be merged.
|
|
m_frame->SchematicCleanUp( &commit );
|
|
|
|
std::vector<SCH_ITEM*> symbols;
|
|
|
|
for( SCH_ITEM* symbol : m_frame->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
|
|
symbols.push_back( symbol );
|
|
|
|
for( SCH_ITEM* symbol : symbols )
|
|
{
|
|
std::vector<VECTOR2I> pts = symbol->GetConnectionPoints();
|
|
|
|
if( pts.size() > 2 )
|
|
continue;
|
|
|
|
for( auto pt = pts.begin(); pt != pts.end(); pt++ )
|
|
{
|
|
for( auto secondPt = pt + 1; secondPt != pts.end(); secondPt++ )
|
|
m_frame->TrimWire( &commit, *pt, *secondPt );
|
|
}
|
|
}
|
|
|
|
for( const VECTOR2I& pt : new_ends )
|
|
{
|
|
if( m_frame->GetScreen()->IsExplicitJunctionNeeded( pt ) )
|
|
m_frame->AddJunction( &commit, m_frame->GetScreen(), pt );
|
|
}
|
|
|
|
if( m_busUnfold.in_progress )
|
|
m_busUnfold = {};
|
|
|
|
for( SCH_ITEM* item : m_frame->GetScreen()->Items() )
|
|
item->ClearEditFlags();
|
|
|
|
commit.Push( _( "Draw Wires" ) );
|
|
}
|
|
|
|
|
|
int SCH_LINE_WIRE_BUS_TOOL::TrimOverLappingWires( SCH_COMMIT* aCommit, EE_SELECTION* aSelection )
|
|
{
|
|
SCHEMATIC* sch = getModel<SCHEMATIC>();
|
|
SCH_SCREEN* screen = sch->CurrentSheet().LastScreen();
|
|
|
|
std::set<SCH_LINE*> lines;
|
|
BOX2I bb = aSelection->GetBoundingBox();
|
|
|
|
for( EDA_ITEM* item : screen->Items().Overlapping( SCH_LINE_T, bb ) )
|
|
lines.insert( static_cast<SCH_LINE*>( item ) );
|
|
|
|
for( unsigned ii = 0; ii < aSelection->GetSize(); ii++ )
|
|
{
|
|
SCH_ITEM* item = dynamic_cast<SCH_ITEM*>( aSelection->GetItem( ii ) );
|
|
|
|
if( !item || !item->IsConnectable() || ( item->Type() == SCH_LINE_T ) )
|
|
continue;
|
|
|
|
std::vector<VECTOR2I> pts = item->GetConnectionPoints();
|
|
|
|
/// If the line intersects with an item in the selection at only two points,
|
|
/// then we can remove the line between the two points.
|
|
for( SCH_LINE* line : lines )
|
|
{
|
|
std::vector<VECTOR2I> conn_pts;
|
|
|
|
for( const VECTOR2I& pt : pts )
|
|
{
|
|
if( IsPointOnSegment( line->GetStartPoint(), line->GetEndPoint(), pt ) )
|
|
conn_pts.push_back( pt );
|
|
|
|
if( conn_pts.size() > 2 )
|
|
break;
|
|
}
|
|
|
|
if( conn_pts.size() == 2 )
|
|
m_frame->TrimWire( aCommit, conn_pts[0], conn_pts[1] );
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int SCH_LINE_WIRE_BUS_TOOL::AddJunctionsIfNeeded( SCH_COMMIT* aCommit, EE_SELECTION* aSelection )
|
|
{
|
|
SCH_SCREEN* screen = m_frame->GetScreen();
|
|
|
|
for( const VECTOR2I& point : screen->GetNeededJunctions( aSelection->Items() ) )
|
|
m_frame->AddJunction( aCommit, m_frame->GetScreen(), point );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void SCH_LINE_WIRE_BUS_TOOL::setTransitions()
|
|
{
|
|
Go( &SCH_LINE_WIRE_BUS_TOOL::DrawSegments, EE_ACTIONS::drawWire.MakeEvent() );
|
|
Go( &SCH_LINE_WIRE_BUS_TOOL::DrawSegments, EE_ACTIONS::drawBus.MakeEvent() );
|
|
Go( &SCH_LINE_WIRE_BUS_TOOL::DrawSegments, EE_ACTIONS::drawLines.MakeEvent() );
|
|
|
|
Go( &SCH_LINE_WIRE_BUS_TOOL::UnfoldBus, EE_ACTIONS::unfoldBus.MakeEvent() );
|
|
}
|