Compare commits

...

2 Commits

Author SHA1 Message Date
eurofun1
266e5cbbd5 Merge branch 'pns-drag-rot' into 'master'
PNS: rotation during component dragging

Closes #9672

See merge request kicad/code/kicad!2270
2025-09-03 12:01:35 +00:00
Alexander Boehm
59ea32eef6 PNS: rotation during component dragging
right click rotates ccw, shift right click cw.
Have not yet figured out how to make this work with
the appropriate hotkey bindings. At least right click
has currently has no other function during 'router dragging'.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/9672
2025-07-13 17:18:11 -04:00
16 changed files with 239 additions and 4 deletions

View File

@ -172,6 +172,17 @@ void CONNECTIVITY_DATA::Move( const VECTOR2I& aDelta )
} );
}
void CONNECTIVITY_DATA::Rotate( const VECTOR2I& aCenter, EDA_ANGLE rot )
{
m_connAlgo->ForEachAnchor(
[&aCenter, rot]( CN_ANCHOR& anchor )
{
VECTOR2I v = anchor.Pos();
RotatePoint( v, aCenter, rot );
anchor.SetPos( v );
} );
}
void CONNECTIVITY_DATA::updateRatsnest()
{

View File

@ -138,6 +138,7 @@ public:
* @param aDelta vector for movement of the tree
*/
void Move( const VECTOR2I& aDelta );
void Rotate( const VECTOR2I& aCenter, EDA_ANGLE rot );
/**
* Function Clear()

View File

@ -76,6 +76,8 @@ public:
const VECTOR2I& Pos() const { return m_pos; }
void SetPos( const VECTOR2I& aPos ) { m_pos = aPos; }
void Move( const VECTOR2I& aPos )
{
m_pos += aPos;

View File

@ -28,6 +28,9 @@
#include "pns_component_dragger.h"
#include "pns_debug_decorator.h"
#include <geometry/shape.h>
#include <geometry/shape_simple.h>
#include <geometry/shape_rect.h>
namespace PNS
{
@ -152,6 +155,107 @@ bool COMPONENT_DRAGGER::Start( const VECTOR2I& aP, ITEM_SET& aPrimitives )
return true;
}
bool COMPONENT_DRAGGER::Transform( const VECTOR2I& aP, const EDA_ANGLE& rot )
{
assert( m_world );
m_world->KillChildren();
m_currentNode = m_world->Branch();
for( ITEM* item : m_initialDraggedItems )
m_currentNode->Remove( item );
m_draggedItems.Clear();
for( SOLID* s : m_solids )
{
std::unique_ptr<SOLID> snew( static_cast<SOLID*>( s->Clone() ) );
// SH_RECT can't be rotated with arbitrary angles, SH_SIMPLE can
if( snew->Shape( 0 )->Type() == SH_RECT )
{
snew->SetShape( new SHAPE_SIMPLE( snew->Hull() ) );
}
snew->Rotate( rot, m_p0 );
VECTOR2I p_next = aP - m_p0 + snew->Pos();
snew->SetPos( p_next );
m_draggedItems.Add( snew.get() );
m_currentNode->Add( std::move( snew ) );
if( !s->IsRoutable() )
continue;
for( DRAGGED_CONNECTION& l : m_conns )
{
if( l.attachedPad == s )
{
l.p_orig = s->Pos() + l.offset;
l.p_next = p_next + l.offset;
}
}
}
for( ITEM* item : m_fixedItems )
{
m_currentNode->Remove( item );
switch( item->Kind() )
{
case ITEM::SEGMENT_T:
{
SEGMENT* s = static_cast<SEGMENT*>( item );
std::unique_ptr<SEGMENT> s_new( s->Clone() );
SEG orig = s->Seg();
VECTOR2I endA = aP - m_p0 + orig.A;
VECTOR2I endB = aP - m_p0 + orig.B;
RotatePoint( endA, aP, rot );
RotatePoint( endB, aP, rot );
s_new->SetEnds( endA, endB );
m_draggedItems.Add( s_new.get() );
m_currentNode->Add( std::move( s_new ) );
break;
}
case ITEM::ARC_T:
{
ARC* a = static_cast<ARC*>( item );
std::unique_ptr<ARC> a_new( a->Clone() );
SHAPE_ARC& arc = a_new->Arc();
arc.Move( aP - m_p0 );
arc.Rotate( rot, aP );
m_draggedItems.Add( a_new.get() );
m_currentNode->Add( std::move( a_new ) );
break;
}
default: wxFAIL_MSG( wxT( "Unexpected item type in COMPONENT_DRAGGER::m_fixedItems" ) );
}
}
for( COMPONENT_DRAGGER::DRAGGED_CONNECTION& cn : m_conns )
{
LINE l_new( cn.origLine );
l_new.Unmark();
l_new.ClearLinks();
l_new.DragCorner( cn.p_next, cn.origLine.CLine().Find( cn.p_orig ) );
PNS_DBG( Dbg(), AddItem, &l_new, BLUE, 0, wxT( "cdrag-new-fanout" ) );
m_draggedItems.Add( l_new );
LINE l_orig( cn.origLine );
m_currentNode->Remove( l_orig );
m_currentNode->Add( l_new );
}
return true;
}
bool COMPONENT_DRAGGER::Drag( const VECTOR2I& aP )
{

View File

@ -59,6 +59,16 @@ public:
*/
bool Drag( const VECTOR2I& aP ) override;
/**
* Function Transform()
*
* Drags the current item(s) to the point aP and rotates them around aP by the given angle.
* Currently only overridden in pns_component_dragger, default implementation just drags.
* @return true, if transformation finished with success.
*/
bool Transform( const VECTOR2I& aP, const EDA_ANGLE& rot ) override;
/**
* Function FixRoute()
*

View File

@ -79,6 +79,16 @@ public:
*/
virtual bool Drag( const VECTOR2I& aP ) = 0;
/**
* Function Transform()
*
* Drags the current item(s) to the point aP and rotates them around aP by the given angle.
* Currently only overridden in pns_component_dragger, default implementation just drags.
* @return true, if transformation finished with success.
*/
virtual bool Transform( const VECTOR2I& aP, const EDA_ANGLE& rot ) { return Drag( aP ); };
/**
* Function FixRoute()
*

View File

@ -736,7 +736,6 @@ bool DRAGGER::FixRoute( bool aForceCommit )
return false;
}
bool DRAGGER::Drag( const VECTOR2I& aP )
{
m_mouseTrailTracer.AddTrailPoint( aP );

View File

@ -128,6 +128,11 @@ void HOLE::Move( const VECTOR2I& delta )
}
void HOLE::Rotate( const VECTOR2I& center, const EDA_ANGLE& rot )
{
m_holeShape->Rotate( rot, center );
}
HOLE* HOLE::MakeCircularHole( const VECTOR2I& pos, int radius, PNS_LAYER_RANGE aLayers )
{
SHAPE_CIRCLE* circle = new SHAPE_CIRCLE( pos, radius );

View File

@ -86,6 +86,7 @@ public:
void SetRadius( int aRadius );
void Move( const VECTOR2I& delta );
void Rotate( const VECTOR2I& center, const EDA_ANGLE& rot );
static HOLE* MakeCircularHole( const VECTOR2I& pos, int radius, PNS_LAYER_RANGE aLayers );

View File

@ -2063,11 +2063,18 @@ void PNS_KICAD_IFACE::modifyBoardItem( PNS::ITEM* aItem )
{
PAD* pad = static_cast<PAD*>( aItem->Parent() );
VECTOR2I pos = static_cast<PNS::SOLID*>( aItem )->Pos();
EDA_ANGLE rot = static_cast<PNS::SOLID*>( aItem )->GetOrientation();
// Don't add to commit; we'll add the parent footprints when processing the m_fpOffsets
FOOTPRINT* fp = pad->GetParentFootprint();
EDA_ANGLE oldr = fp->GetOrientation();
fp->SetOrientation( rot );
m_fpOffsets[pad].p_old = pad->GetPosition();
fp->SetOrientation( oldr );
m_fpOffsets[pad].p_new = pos;
m_fpOffsets[pad].r_old = pad->GetOrientation();
m_fpOffsets[pad].r_new = rot;
}
break;
}
@ -2226,13 +2233,16 @@ void PNS_KICAD_IFACE::Commit()
FOOTPRINT* footprint = pad->GetParentFootprint();
VECTOR2I p_orig = footprint->GetPosition();
VECTOR2I p_new = p_orig + offset;
EDA_ANGLE roffs = fpOffset.r_new - fpOffset.r_old;
EDA_ANGLE r_orig = footprint->GetOrientation();
EDA_ANGLE r_new = r_orig + roffs;
if( processedFootprints.find( footprint ) != processedFootprints.end() )
continue;
processedFootprints.insert( footprint );
m_commit->Modify( footprint );
footprint->SetPosition( p_new );
footprint->SetOrientation( r_new );
}
m_fpOffsets.clear();

View File

@ -173,6 +173,7 @@ protected:
struct OFFSET
{
VECTOR2I p_old, p_new;
EDA_ANGLE r_old, r_new;
};
std::map<PAD*, OFFSET> m_fpOffsets;

View File

@ -488,6 +488,24 @@ bool ROUTER::Move( const VECTOR2I& aP, ITEM* endItem )
return false;
}
bool ROUTER::Transform( const VECTOR2I& aP, const EDA_ANGLE& rot, ITEM* endItem )
{
if( m_logger )
m_logger->Log( LOGGER::EVT_MOVE, aP, endItem );
switch( m_state )
{
case ROUTE_TRACK: return movePlacing( aP, endItem );
case DRAG_SEGMENT:
case DRAG_COMPONENT: return rotDragging( aP, rot, endItem );
default: break;
}
GetRuleResolver()->ClearTemporaryCaches();
return false;
}
bool ROUTER::GetNearestRatnestAnchor( VECTOR2I& aOtherEnd, PNS_LAYER_RANGE& aOtherEndLayers,
ITEM*& aOtherEndItem )
@ -640,6 +658,19 @@ bool ROUTER::moveDragging( const VECTOR2I& aP, ITEM* aEndItem )
return ret;
}
bool ROUTER::rotDragging( const VECTOR2I& aP, const EDA_ANGLE& rot, ITEM* aEndItem )
{
m_iface->EraseView();
bool ret = m_dragger->Transform( aP, rot );
ITEM_SET dragged = m_dragger->Traces();
m_leaderSegments = m_dragger->GetLastCommittedLeaderSegments();
updateView( m_dragger->CurrentNode(), dragged, true );
return ret;
}
void ROUTER::markViolations( NODE* aNode, ITEM_SET& aCurrent, NODE::ITEM_VECTOR& aRemoved )
{

View File

@ -161,6 +161,7 @@ public:
bool RoutingInProgress() const;
bool StartRouting( const VECTOR2I& aP, ITEM* aItem, int aLayer );
bool Move( const VECTOR2I& aP, ITEM* aItem );
bool Transform( const VECTOR2I& aP, const EDA_ANGLE& rot, ITEM* aItem );
bool Finish();
bool ContinueFromEnd( ITEM** aNewStartItem );
bool FixRoute( const VECTOR2I& aP, ITEM* aItem, bool aForceFinish, bool aForceCommit );
@ -241,6 +242,7 @@ public:
private:
bool movePlacing( const VECTOR2I& aP, ITEM* aItem );
bool moveDragging( const VECTOR2I& aP, ITEM* aItem );
bool rotDragging( const VECTOR2I& aP, const EDA_ANGLE& rot, ITEM* aItem );
void updateView( NODE* aNode, ITEM_SET& aCurrent, bool aDragging = false );

View File

@ -77,6 +77,21 @@ ITEM* SOLID::Clone() const
return solid;
}
void SOLID::Rotate( const EDA_ANGLE& a, const VECTOR2I& center )
{
if( m_shape )
{
m_shape->Rotate( a, center );
RotatePoint( m_pos, center, a );
}
if( m_hole )
{
m_hole->Rotate( center, a );
}
m_orientation = ( m_orientation + a );
}
void SOLID::SetPos( const VECTOR2I& aCenter )
{

View File

@ -136,6 +136,8 @@ public:
EDA_ANGLE GetOrientation() const { return m_orientation; }
void SetOrientation( const EDA_ANGLE& aOrientation ) { m_orientation = aOrientation; }
void Rotate( const EDA_ANGLE& aOrientation, const VECTOR2I& center );
virtual void SetHole( HOLE* aHole ) override
{
if( m_hole && m_hole->BelongsTo( this ) )

View File

@ -2377,6 +2377,8 @@ int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent )
bool hasMouseMoved = false;
bool hasMultidragCancelled = false;
EDA_ANGLE rot = EDA_ANGLE( 0.0 );
EDA_ANGLE rotationAngle = getEditFrame<PCB_BASE_EDIT_FRAME>()->GetRotationAngle();
while( TOOL_EVENT* evt = Wait() )
{
@ -2391,11 +2393,19 @@ int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent )
break;
}
else if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
else if( evt->IsMotion() || evt->IsAction( &PCB_ACTIONS::rotateCw )
|| evt->IsAction( &PCB_ACTIONS::rotateCcw ) )
{
// passing rotation angles >360 or <-360 to downstream functions does not seem to cause problems
if( evt->IsAction( &PCB_ACTIONS::rotateCw ) )
rot = rot - rotationAngle;
if( evt->IsAction( &PCB_ACTIONS::rotateCcw ) )
rot = rot + rotationAngle;
hasMouseMoved = true;
updateEndItem( *evt );
m_router->Move( m_endSnapPoint, m_endItem );
m_router->Transform( m_endSnapPoint, EDA_ANGLE( rot ), m_endItem );
view()->ClearPreview();
@ -2403,6 +2413,7 @@ int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent )
{
VECTOR2I offset = m_endSnapPoint - p;
BOARD_ITEM* previewItem;
VECTOR2I center = m_endSnapPoint;
for( FOOTPRINT* footprint : footprints )
{
@ -2410,6 +2421,7 @@ int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent )
{
previewItem = static_cast<BOARD_ITEM*>( drawing->Clone() );
previewItem->Move( offset );
previewItem->Rotate( center, rot );
view()->AddToPreview( previewItem );
view()->Hide( drawing, true );
@ -2422,6 +2434,7 @@ int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent )
{
previewItem = static_cast<BOARD_ITEM*>( pad->Clone() );
previewItem->Move( offset );
previewItem->Rotate( center, rot );
view()->AddToPreview( previewItem );
}
@ -2435,16 +2448,21 @@ int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent )
previewItem = static_cast<BOARD_ITEM*>( footprint->Reference().Clone() );
previewItem->Move( offset );
previewItem->Rotate( center, rot );
view()->AddToPreview( previewItem );
view()->Hide( &footprint->Reference() );
previewItem = static_cast<BOARD_ITEM*>( footprint->Value().Clone() );
previewItem->Move( offset );
previewItem->Rotate( center, rot );
view()->AddToPreview( previewItem );
view()->Hide( &footprint->Value() );
if( showCourtyardConflicts )
{
footprint->Move( offset );
footprint->Rotate( center, rot );
}
}
if( showCourtyardConflicts )
@ -2453,11 +2471,21 @@ int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent )
courtyardClearanceDRC.UpdateConflicts( getView(), false );
for( FOOTPRINT* footprint : footprints )
{
footprint->Rotate( center, -rot );
footprint->Move( -offset );
}
}
// Update ratsnest
dynamicData->Move( offset - lastOffset );
if( evt->IsAction( &PCB_ACTIONS::rotateCw ) )
dynamicData->Rotate( center, -getEditFrame<PCB_BASE_EDIT_FRAME>()->GetRotationAngle() );
if( evt->IsAction( &PCB_ACTIONS::rotateCcw ) )
dynamicData->Rotate( center, getEditFrame<PCB_BASE_EDIT_FRAME>()->GetRotationAngle() );
lastOffset = offset;
connectivityData->ComputeLocalRatsnest( dynamicItems, dynamicData.get(), offset );
}
@ -2494,6 +2522,9 @@ int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent )
break;
}
else if( evt->IsClick( BUT_RIGHT ) )
{
}
else if( evt->IsUndoRedo() )
{
// We're in an UndoRedoBlock. If we get here, something's broken.