kicad-source/pcbnew/tools/point_editor.cpp
Seth Hillbrand 1e461c2259 ADDED: Improved center point dragging
The center point on polygons now maintains the axis of the dragged line
and, optionally with Ctrl pressed, maintains the slope of the adjacent
segments as well.

This also fixes a longstanding issue that prevented the ctrl-snapping
from using the original point rather than the last updated point when
constraining.

Fixes https://gitlab.com/kicad/code/kicad/issues/2465
2020-08-27 10:34:12 -07:00

1783 lines
62 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2019 CERN
* @author Maciej Suminski <maciej.suminski@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 <memory>
using namespace std::placeholders;
#include <advanced_config.h>
#include <tool/tool_manager.h>
#include <view/view_controls.h>
#include <gal/graphics_abstraction_layer.h>
#include <geometry/seg.h>
#include <confirm.h>
#include "pcb_actions.h"
#include "selection_tool.h"
#include "point_editor.h"
#include "grid_helper.h"
#include <board_commit.h>
#include <bitmaps.h>
#include <status_popup.h>
#include <pcb_edit_frame.h>
#include <class_edge_mod.h>
#include <class_dimension.h>
#include <class_zone.h>
#include <connectivity/connectivity_data.h>
#include <widgets/progress_reporter.h>
// Few constants to avoid using bare numbers for point indices
enum SEG_POINTS
{
SEG_START, SEG_END
};
enum RECT_POINTS
{
RECT_TOP_LEFT, RECT_TOP_RIGHT, RECT_BOT_RIGHT, RECT_BOT_LEFT
};
enum ARC_POINTS
{
ARC_CENTER, ARC_START, ARC_MID, ARC_END
};
enum CIRCLE_POINTS
{
CIRC_CENTER, CIRC_END
};
enum BEZIER_CURVE_POINTS
{
BEZIER_CURVE_START,
BEZIER_CURVE_CONTROL_POINT1,
BEZIER_CURVE_CONTROL_POINT2,
BEZIER_CURVE_END
};
enum DIMENSION_POINTS
{
DIM_CROSSBARO,
DIM_CROSSBARF,
DIM_FEATUREGO,
DIM_FEATUREDO,
};
class EDIT_POINTS_FACTORY
{
private:
static void buildForPolyOutline( std::shared_ptr<EDIT_POINTS> points,
const SHAPE_POLY_SET* aOutline, KIGFX::GAL* aGal )
{
int cornersCount = aOutline->TotalVertices();
for( auto iterator = aOutline->CIterateWithHoles(); iterator; iterator++ )
{
points->AddPoint( *iterator );
if( iterator.IsEndContour() )
points->AddBreak();
}
// Lines have to be added after creating edit points,
// as they use EDIT_POINT references
for( int i = 0; i < cornersCount - 1; ++i )
{
if( points->IsContourEnd( i ) )
{
points->AddLine( points->Point( i ),
points->Point( points->GetContourStartIdx( i ) ) );
}
else
{
points->AddLine( points->Point( i ), points->Point( i + 1 ) );
}
points->Line( i ).SetConstraint( new EC_PERPLINE( points->Line( i ) ) );
}
// The last missing line, connecting the last and the first polygon point
points->AddLine( points->Point( cornersCount - 1 ),
points->Point( points->GetContourStartIdx( cornersCount - 1 ) ) );
points->Line( points->LinesSize() - 1 ).SetConstraint(
new EC_PERPLINE( points->Line( points->LinesSize() - 1 ) ) );
}
public:
static std::shared_ptr<EDIT_POINTS> Make( EDA_ITEM* aItem, KIGFX::GAL* aGal )
{
std::shared_ptr<EDIT_POINTS> points = std::make_shared<EDIT_POINTS>( aItem );
if( !aItem )
return points;
// Generate list of edit points basing on the item type
switch( aItem->Type() )
{
case PCB_LINE_T:
case PCB_MODULE_EDGE_T:
{
const DRAWSEGMENT* segment = static_cast<const DRAWSEGMENT*>( aItem );
switch( segment->GetShape() )
{
case S_SEGMENT:
points->AddPoint( segment->GetStart() );
points->AddPoint( segment->GetEnd() );
break;
case S_RECT:
points->AddPoint( segment->GetStart() );
points->AddPoint( wxPoint( segment->GetEnd().x, segment->GetStart().y ) );
points->AddPoint( segment->GetEnd() );
points->AddPoint( wxPoint( segment->GetStart().x, segment->GetEnd().y ) );
break;
case S_ARC:
points->AddPoint( segment->GetCenter() );
points->AddPoint( segment->GetArcStart() );
points->AddPoint( segment->GetArcMid() );
points->AddPoint( segment->GetArcEnd() );
// Set constraints
// Arc end has to stay at the same radius as the start
points->Point( ARC_END ).SetConstraint( new EC_CIRCLE( points->Point( ARC_END ),
points->Point( ARC_CENTER ),
points->Point( ARC_START ) ) );
points->Point( ARC_MID ).SetConstraint( new EC_LINE( points->Point( ARC_MID ),
points->Point( ARC_CENTER ) ) );
break;
case S_CIRCLE:
points->AddPoint( segment->GetCenter() );
points->AddPoint( segment->GetEnd() );
break;
case S_POLYGON:
buildForPolyOutline( points, &segment->GetPolyShape(), aGal );
break;
case S_CURVE:
points->AddPoint( segment->GetStart() );
points->AddPoint( segment->GetBezControl1() );
points->AddPoint( segment->GetBezControl2() );
points->AddPoint( segment->GetEnd() );
break;
default: // suppress warnings
break;
}
break;
}
case PCB_PAD_T:
{
const D_PAD* pad = static_cast<const D_PAD*>( aItem );
wxPoint shapePos = pad->ShapePos();
wxPoint halfSize( pad->GetSize().x / 2, pad->GetSize().y / 2 );
if( pad->GetParent() && pad->GetParent()->PadsLocked() )
break;
switch( pad->GetShape() )
{
case PAD_SHAPE_CIRCLE:
points->AddPoint( shapePos );
points->AddPoint( wxPoint( shapePos.x + halfSize.x, shapePos.y ) );
break;
case PAD_SHAPE_OVAL:
case PAD_SHAPE_TRAPEZOID:
case PAD_SHAPE_RECT:
case PAD_SHAPE_ROUNDRECT:
case PAD_SHAPE_CHAMFERED_RECT:
{
if( (int) pad->GetOrientation() % 900 != 0 )
break;
if( pad->GetOrientation() == 900 || pad->GetOrientation() == 2700 )
std::swap( halfSize.x, halfSize.y );
points->AddPoint( shapePos - halfSize );
points->AddPoint( wxPoint( shapePos.x + halfSize.x, shapePos.y - halfSize.y ) );
points->AddPoint( shapePos + halfSize );
points->AddPoint( wxPoint( shapePos.x - halfSize.x, shapePos.y + halfSize.y ) );
}
break;
default: // suppress warnings
break;
}
}
break;
case PCB_MODULE_ZONE_AREA_T:
case PCB_ZONE_AREA_T:
{
auto zone = static_cast<const ZONE_CONTAINER*>( aItem );
buildForPolyOutline( points, zone->Outline(), aGal );
}
break;
case PCB_DIMENSION_T:
{
const DIMENSION* dimension = static_cast<const DIMENSION*>( aItem );
points->AddPoint( dimension->m_crossBarO );
points->AddPoint( dimension->m_crossBarF );
points->AddPoint( dimension->m_featureLineGO );
points->AddPoint( dimension->m_featureLineDO );
// Dimension height setting - edit points should move only along the feature lines
points->Point( DIM_CROSSBARO ).SetConstraint( new EC_LINE( points->Point( DIM_CROSSBARO ),
points->Point( DIM_FEATUREGO ) ) );
points->Point( DIM_CROSSBARF ).SetConstraint( new EC_LINE( points->Point( DIM_CROSSBARF ),
points->Point( DIM_FEATUREDO ) ) );
}
break;
default:
points.reset();
break;
}
return points;
}
private:
EDIT_POINTS_FACTORY() {};
};
POINT_EDITOR::POINT_EDITOR() :
PCB_TOOL_BASE( "pcbnew.PointEditor" ),
m_selectionTool( NULL ),
m_editedPoint( NULL ),
m_original( VECTOR2I( 0, 0 ) ),
m_altConstrainer( VECTOR2I( 0, 0 ) ),
m_refill( false )
{
}
void POINT_EDITOR::Reset( RESET_REASON aReason )
{
m_refill = false;
m_editPoints.reset();
m_altConstraint.reset();
getViewControls()->SetAutoPan( false );
m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( getEditFrame<PCB_BASE_EDIT_FRAME>() );
m_statusPopup->SetTextColor( wxColour( 255, 0, 0 ) );
m_statusPopup->SetText( _( "Self-intersecting polygons are not allowed." ) );
}
bool POINT_EDITOR::Init()
{
// Find the selection tool, so they can cooperate
m_selectionTool = m_toolMgr->GetTool<SELECTION_TOOL>();
wxASSERT_MSG( m_selectionTool, _( "pcbnew.InteractiveSelection tool is not available" ) );
auto& menu = m_selectionTool->GetToolMenu().GetMenu();
menu.AddItem( PCB_ACTIONS::pointEditorAddCorner, POINT_EDITOR::addCornerCondition );
menu.AddItem( PCB_ACTIONS::pointEditorRemoveCorner,
std::bind( &POINT_EDITOR::removeCornerCondition, this, _1 ) );
return true;
}
void POINT_EDITOR::updateEditedPoint( const TOOL_EVENT& aEvent )
{
EDIT_POINT* point;
if( aEvent.IsMotion() )
{
point = m_editPoints->FindPoint( aEvent.Position(), getView() );
}
else if( aEvent.IsDrag( BUT_LEFT ) )
{
point = m_editPoints->FindPoint( aEvent.DragOrigin(), getView() );
}
else
{
point = m_editPoints->FindPoint( getViewControls()->GetCursorPosition(), getView() );
}
if( m_editedPoint != point )
setEditedPoint( point );
}
int POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent )
{
if( !m_selectionTool )
return 0;
const PCBNEW_SELECTION& selection = m_selectionTool->GetSelection();
if( selection.Size() != 1 || selection.Front()->GetEditFlags() )
return 0;
Activate();
KIGFX::VIEW_CONTROLS* controls = getViewControls();
KIGFX::VIEW* view = getView();
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
controls->ShowCursor( true );
GRID_HELPER grid( m_toolMgr, editFrame->GetMagneticItemsSettings() );
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
if( !item )
return 0;
m_editPoints = EDIT_POINTS_FACTORY::Make( item, getView()->GetGAL() );
if( !m_editPoints )
return 0;
view->Add( m_editPoints.get() );
setEditedPoint( nullptr );
updateEditedPoint( aEvent );
m_refill = false;
bool inDrag = false;
BOARD_COMMIT commit( editFrame );
LSET snapLayers = item->GetLayerSet();
if( item->Type() == PCB_DIMENSION_T )
snapLayers = LSET::AllLayersMask();
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() )
{
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
grid.SetUseGrid( !evt->Modifier( MD_ALT ) );
controls->SetSnapping( !evt->Modifier( MD_ALT ) );
if( !m_editPoints || evt->IsSelectionEvent() )
break;
if ( !inDrag )
updateEditedPoint( *evt );
if( evt->IsDrag( BUT_LEFT ) && m_editedPoint )
{
if( !inDrag )
{
commit.StageItems( selection, CHT_MODIFY );
controls->ForceCursorPosition( false );
m_original = *m_editedPoint; // Save the original position
controls->SetAutoPan( true );
inDrag = true;
grid.SetAuxAxes( true, m_original.GetPosition() );
setAltConstraint( true );
m_editedPoint->SetActive();
}
//TODO: unify the constraints to solve simultaneously instead of sequentially
m_editedPoint->SetPosition( grid.BestSnapAnchor( evt->Position(),
snapLayers, { item } ) );
// The alternative constraint limits to 45°
bool enableAltConstraint = !!evt->Modifier( MD_CTRL );
if( enableAltConstraint )
m_altConstraint->Apply();
else
m_editedPoint->ApplyConstraint();
// Don't snap the line center to the grid
if( !dynamic_cast<EDIT_LINE*>( m_editedPoint) )
m_editedPoint->SetPosition( grid.BestSnapAnchor( m_editedPoint->GetPosition(),
snapLayers, { item } ) );
updateItem();
updatePoints();
}
else if( inDrag && evt->IsMouseUp( BUT_LEFT ) )
{
if( m_editedPoint )
{
m_editedPoint->SetActive( false );
getView()->Update( m_editPoints.get() );
}
controls->SetAutoPan( false );
setAltConstraint( false );
commit.Push( _( "Drag a corner" ) );
inDrag = false;
m_refill = true;
}
else if( evt->IsCancelInteractive() || evt->IsActivate() )
{
if( inDrag ) // Restore the last change
commit.Revert();
else if( evt->IsCancelInteractive() )
break;
if( evt->IsActivate() && !evt->IsMoveTool() )
break;
}
else
evt->SetPassEvent();
}
if( m_editPoints )
{
view->Remove( m_editPoints.get() );
finishItem();
m_editPoints.reset();
}
frame()->UpdateMsgPanel();
return 0;
}
/**
* Update the coordinates of 4 corners of a rectangle, according to pad constraints and the
* moved corner
* @param aEditedPointIndex is the corner id
* @param aMinWidth is the minimal width constraint
* @param aMinHeight is the minimal height constraint
* @param aTopLeft [in/out] is the RECT_TOPLEFT to constraint
* @param aTopRight [in/out] is the RECT_TOPRIGHT to constraint
* @param aBotLeft [in/out] is the RECT_BOTLEFT to constraint
* @param aBotRight [in/out] is the RECT_BOTRIGHT to constraint
* @param aHole the location of the pad's hole
* @param aHoleSize the pad's hole size (or {0,0} if it has no hole)
*/
static void pinEditedCorner( int aEditedPointIndex, int aMinWidth, int aMinHeight,
VECTOR2I& aTopLeft, VECTOR2I& aTopRight, VECTOR2I& aBotLeft,
VECTOR2I& aBotRight, VECTOR2I aHole, VECTOR2I aHoleSize )
{
switch( aEditedPointIndex )
{
case RECT_TOP_LEFT:
if( aHoleSize.x )
{
// pin edited point to the top/left of the hole
aTopLeft.x = std::min( aTopLeft.x, aHole.x - aHoleSize.x / 2 - aMinWidth );
aTopLeft.y = std::min( aTopLeft.y, aHole.y - aHoleSize.y / 2 - aMinHeight );
}
else
{
// pin edited point within opposite corner
aTopLeft.x = std::min( aTopLeft.x, aBotRight.x - aMinWidth );
aTopLeft.y = std::min( aTopLeft.y, aBotRight.y - aMinHeight );
}
// push edited point edges to adjacent corners
aTopRight.y = aTopLeft.y;
aBotLeft.x = aTopLeft.x;
break;
case RECT_TOP_RIGHT:
if( aHoleSize.x )
{
// pin edited point to the top/right of the hole
aTopRight.x = std::max( aTopRight.x, aHole.x + aHoleSize.x / 2 + aMinWidth );
aTopRight.y = std::min( aTopRight.y, aHole.y - aHoleSize.y / 2 - aMinHeight );
}
else
{
// pin edited point within opposite corner
aTopRight.x = std::max( aTopRight.x, aBotLeft.x + aMinWidth );
aTopRight.y = std::min( aTopRight.y, aBotLeft.y - aMinHeight );
}
// push edited point edges to adjacent corners
aTopLeft.y = aTopRight.y;
aBotRight.x = aTopRight.x;
break;
case RECT_BOT_LEFT:
if( aHoleSize.x )
{
// pin edited point to the bottom/left of the hole
aBotLeft.x = std::min( aBotLeft.x, aHole.x - aHoleSize.x / 2 - aMinWidth );
aBotLeft.y = std::max( aBotLeft.y, aHole.y + aHoleSize.y / 2 + aMinHeight );
}
else
{
// pin edited point within opposite corner
aBotLeft.x = std::min( aBotLeft.x, aTopRight.x - aMinWidth );
aBotLeft.y = std::max( aBotLeft.y, aTopRight.y + aMinHeight );
}
// push edited point edges to adjacent corners
aBotRight.y = aBotLeft.y;
aTopLeft.x = aBotLeft.x;
break;
case RECT_BOT_RIGHT:
if( aHoleSize.x )
{
// pin edited point to the bottom/right of the hole
aBotRight.x = std::max( aBotRight.x, aHole.x + aHoleSize.x / 2 + aMinWidth );
aBotRight.y = std::max( aBotRight.y, aHole.y + aHoleSize.y / 2 + aMinHeight );
}
else
{
// pin edited point within opposite corner
aBotRight.x = std::max( aBotRight.x, aTopLeft.x + aMinWidth );
aBotRight.y = std::max( aBotRight.y, aTopLeft.y + aMinHeight );
}
// push edited point edges to adjacent corners
aBotLeft.y = aBotRight.y;
aTopRight.x = aBotRight.x;
break;
}
}
void POINT_EDITOR::updateItem() const
{
EDA_ITEM* item = m_editPoints->GetParent();
const BOARD_DESIGN_SETTINGS& boardSettings = board()->GetDesignSettings();
if( !item )
return;
switch( item->Type() )
{
case PCB_LINE_T:
case PCB_MODULE_EDGE_T:
{
DRAWSEGMENT* segment = static_cast<DRAWSEGMENT*>( item );
switch( segment->GetShape() )
{
case S_SEGMENT:
if( isModified( m_editPoints->Point( SEG_START ) ) )
segment->SetStart( wxPoint( m_editPoints->Point( SEG_START ).GetPosition().x,
m_editPoints->Point( SEG_START ).GetPosition().y ) );
else if( isModified( m_editPoints->Point( SEG_END ) ) )
segment->SetEnd( wxPoint( m_editPoints->Point( SEG_END ).GetPosition().x,
m_editPoints->Point( SEG_END ).GetPosition().y ) );
break;
case S_RECT:
{
if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) ) )
{
segment->SetStart( (wxPoint) m_editPoints->Point( RECT_TOP_LEFT ).GetPosition() );
}
else if( isModified( m_editPoints->Point( RECT_TOP_RIGHT ) ) )
{
segment->SetStartY( m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition().y );
segment->SetEndX( m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition().x );
}
else if( isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) )
{
segment->SetEnd( (wxPoint) m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition() );
}
else if( isModified( m_editPoints->Point( RECT_BOT_LEFT ) ) )
{
segment->SetStartX( m_editPoints->Point( RECT_BOT_LEFT ).GetPosition().x );
segment->SetEndY( m_editPoints->Point( RECT_BOT_LEFT ).GetPosition().y );
}
}
break;
case S_ARC:
{
VECTOR2I center = m_editPoints->Point( ARC_CENTER ).GetPosition();
VECTOR2I mid = m_editPoints->Point( ARC_MID ).GetPosition();
VECTOR2I start = m_editPoints->Point( ARC_START ).GetPosition();
VECTOR2I end = m_editPoints->Point( ARC_END ).GetPosition();
if( center != segment->GetCenter() )
{
wxPoint moveVector = wxPoint( center.x, center.y ) - segment->GetCenter();
segment->Move( moveVector );
m_editPoints->Point( ARC_START ).SetPosition( segment->GetArcStart() );
m_editPoints->Point( ARC_END ).SetPosition( segment->GetArcEnd() );
m_editPoints->Point( ARC_MID ).SetPosition( segment->GetArcMid() );
}
else
{
const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
VECTOR2I oldCenter = segment->GetCenter();
double newAngle;
bool clockwise;
if( mid != segment->GetArcMid() )
{
// This allows the user to go on the sides of the arc
mid = cursorPos;
// Find the new center
center = GetArcCenter( start, mid, end );
segment->SetCenter( wxPoint( center.x, center.y ) );
m_editPoints->Point( ARC_CENTER ).SetPosition( center );
// Check if the new arc is CW or CCW
VECTOR2D startLine = start - center;
VECTOR2D endLine = end - center;
newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
VECTOR2D v1, v2;
v1 = start - mid;
v2 = end - mid;
double theta = RAD2DECIDEG( v1.Angle() );
RotatePoint( &( v1.x ), &( v1.y ), theta );
RotatePoint( &( v2.x ), &( v2.y ), theta );
clockwise = ( ( v1.Angle() - v2.Angle() ) > 0 );
// Normalize the angle
if( clockwise && newAngle < 0.0 )
newAngle += 3600.0;
else if( !clockwise && newAngle > 0.0 )
newAngle -= 3600.0;
// Accuracy test
// First, get the angle
VECTOR2I endTest = start;
RotatePoint( &( endTest.x ), &( endTest.y ), center.x, center.y, -newAngle );
double distance = ( endTest - end ).SquaredEuclideanNorm();
if( distance > ADVANCED_CFG::GetCfg().m_drawArcAccuracy )
{
// Cancel Everything
// If the accuracy is low, we can't draw precisely the arc.
// It may happen when the radius is *high*
segment->SetCenter( wxPoint( oldCenter.x, oldCenter.y ) );
}
else
{
segment->SetAngle( newAngle );
segment->SetArcEnd( wxPoint( end.x, end.y ) );
}
// Now, update the edit point position
// Express the point in a cercle-centered coordinate system.
mid = cursorPos - center;
double sqRadius = ( end - center ).SquaredEuclideanNorm();
// Special case, because the tangent would lead to +/- infinity
if( mid.x == 0 )
{
mid.y = mid.y > 0 ? sqrt( sqRadius ) : -sqrt( sqRadius );
}
else
{
// Circle : x^2 + y^2 = R ^ 2
// In this coordinate system, the angular position of the cursor is (r, theta)
// The line coming from the center of the circle is y = start.y / start.x * x
// The intersection fulfills : x^2 = R^2 / ( 1 + ( start.y / start.x ) ^ 2 )
double tmp = sqrt( sqRadius /
( ( 1.0 + mid.y / static_cast<double>( mid.x ) * mid.y
/ static_cast<double>( mid.x ) ) ) );
// Move to the correct quadrant
tmp = mid.x > 0 ? tmp : -tmp;
mid.y = mid.y / static_cast<double>( mid.x ) * tmp;
mid.x = tmp;
}
// Go back to the main coordinate system
mid = mid + center;
m_editPoints->Point( ARC_MID ).SetPosition( mid );
}
else if( ( start != segment->GetArcStart() ) || ( end != segment->GetArcEnd() ) )
{
VECTOR2D startLine = start - center;
VECTOR2D endLine = end - center;
newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
VECTOR2I *p1, *p2, *p3;
// p1 does not move, p2 does.
bool movingStart;
bool arcValid = true;
if( start != segment->GetArcStart() )
{
start = cursorPos;
p1 = &end;
p2 = &start;
p3 = &mid;
movingStart = true;
}
else
{
end = cursorPos;
p1 = &start;
p2 = &end;
p3 = &mid;
movingStart = false;
}
VECTOR2D v1, v2, v3, v4;
// Move the coordinate system
v1 = *p1 - center;
v2 = *p2 - center;
v3 = *p3 - center;
VECTOR2D u1, u2, u3;
u1 = v1 / v1.EuclideanNorm();
u2 = v3 - ( u1.x * v3.x + u1.y * v3.y ) * u1;
u2 = u2 / u2.EuclideanNorm();
// [ u1, u3 ] is a base centered on the circle with:
// u1 : unit vector toward the point that does not move
// u2 : unit vector toward the mid point.
// Get vectors v1, and v2 in that coordinate system.
double det = u1.x * u2.y - u2.x * u1.y;
double tmpx = v1.x * u2.y - v1.y * u2.x;
double tmpy = -v1.x * u1.y + v1.y * u1.x;
v1.x = tmpx;
v1.y = tmpy;
v1 = v1 / det;
tmpx = v2.x * u2.y - v2.y * u2.x;
tmpy = -v2.x * u1.y + v2.y * u1.x;
v2.x = tmpx;
v2.y = tmpy;
v2 = v2 / det;
double R = v1.EuclideanNorm();
bool transformCircle = false;
/* p2
* X***
* ** <---- This is the arc
* y ^ **
* | R *
* | <-----------> *
* x------x------>--------x p1
* C' <----> C x
* delta
*
* p1 does not move, and the tangent at p1 remains the same.
* => The new center, C', will be on the C-p1 axis.
* p2 moves
*
* The radius of the new circle is delta + R
*
* || C' p2 || = || C' P1 ||
* is the same as :
* ( delta + p2.x ) ^ 2 + p2.y ^ 2 = ( R + delta ) ^ 2
*
* delta = ( R^2 - p2.x ^ 2 - p2.y ^2 ) / ( 2 * p2.x - 2 * R )
*
* We can use this equation for any point p2 with p2.x < R
*/
if( v2.x == R )
{
// Straight line, do nothing
}
else
{
if( v2.x > R )
{
// If we need to invert the curvature.
// We modify the input so we can use the same equation
transformCircle = true;
v2.x = 2 * R - v2.x;
}
// We can keep the tangent constraint.
double delta = ( R * R - v2.x * v2.x - v2.y * v2.y ) / ( 2 * v2.x - 2 * R );
// This is just to limit the radius, so nothing overflows later when drawing.
if( abs( v2.y / ( R - v2.x ) )
> ADVANCED_CFG::GetCfg().m_drawArcCenterMaxAngle )
{
arcValid = false;
}
// v4 is the new center
v4 = ( !transformCircle ) ? VECTOR2D( -delta, 0 ) :
VECTOR2D( 2 * R + delta, 0 );
clockwise = segment->GetAngle() > 0;
if( transformCircle )
clockwise = !clockwise;
tmpx = v4.x * u1.x + v4.y * u2.x;
tmpy = v4.x * u1.y + v4.y * u2.y;
v4.x = tmpx;
v4.y = tmpy;
center = v4 + center;
startLine = start - center;
endLine = end - center;
newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
if( clockwise && newAngle < 0.0 )
newAngle += 3600.0;
else if( !clockwise && newAngle > 0.0 )
newAngle -= 3600.0;
if( arcValid )
{
segment->SetAngle( newAngle );
segment->SetCenter( wxPoint( center.x, center.y ) );
if( movingStart )
{
segment->SetArcStart( wxPoint( start.x, start.y ) );
// Set angle computes the end point, so re-force it now.
segment->SetArcEnd( wxPoint( end.x, end.y ) );
m_editPoints->Point( ARC_START ).SetPosition( start );
}
else
{
segment->SetArcEnd( wxPoint( end.x, end.y ) );
m_editPoints->Point( ARC_END ).SetPosition( end );
}
m_editPoints->Point( ARC_CENTER ).SetPosition( center );
}
}
}
}
}
break;
case S_CIRCLE:
{
const VECTOR2I& center = m_editPoints->Point( CIRC_CENTER ).GetPosition();
const VECTOR2I& end = m_editPoints->Point( CIRC_END ).GetPosition();
if( isModified( m_editPoints->Point( CIRC_CENTER ) ) )
{
wxPoint moveVector = wxPoint( center.x, center.y ) - segment->GetCenter();
segment->Move( moveVector );
}
else
{
segment->SetEnd( wxPoint( end.x, end.y ) );
}
}
break;
case S_POLYGON:
{
SHAPE_POLY_SET& outline = segment->GetPolyShape();
for( int i = 0; i < outline.TotalVertices(); ++i )
outline.SetVertex( i, m_editPoints->Point( i ).GetPosition() );
for( unsigned i = 0; i < m_editPoints->LinesSize(); ++i )
{
if( !isModified( m_editPoints->Line( i ) ) )
m_editPoints->Line( i ).SetConstraint( new EC_PERPLINE( m_editPoints->Line( i ) ) );
}
validatePolygon( outline );
}
break;
case S_CURVE:
if( isModified( m_editPoints->Point( BEZIER_CURVE_START ) ) )
segment->SetStart( wxPoint( m_editPoints->Point( BEZIER_CURVE_START ).GetPosition().x,
m_editPoints->Point( BEZIER_CURVE_START ).GetPosition().y ) );
else if( isModified( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ) ) )
segment->SetBezControl1( wxPoint( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ).GetPosition().x,
m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ).GetPosition().y ) );
else if( isModified( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ) ) )
segment->SetBezControl2( wxPoint( m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ).GetPosition().x,
m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ).GetPosition().y ) );
else if( isModified( m_editPoints->Point( BEZIER_CURVE_END ) ) )
segment->SetEnd( wxPoint( m_editPoints->Point( BEZIER_CURVE_END ).GetPosition().x,
m_editPoints->Point( BEZIER_CURVE_END ).GetPosition().y ) );
segment->RebuildBezierToSegmentsPointsList( segment->GetWidth() );
break;
default: // suppress warnings
break;
}
// Update relative coordinates for module edges
if( EDGE_MODULE* edge = dyn_cast<EDGE_MODULE*>( item ) )
edge->SetLocalCoord();
break;
}
case PCB_PAD_T:
{
D_PAD* pad = static_cast<D_PAD*>( item );
switch( pad->GetShape() )
{
case PAD_SHAPE_CIRCLE:
{
wxPoint center = (wxPoint) m_editPoints->Point( CIRC_CENTER ).GetPosition();
wxPoint end = (wxPoint) m_editPoints->Point( CIRC_END ).GetPosition();
if( isModified( m_editPoints->Point( CIRC_CENTER ) ) )
{
wxPoint moveVector = center - pad->ShapePos();
pad->SetOffset( pad->GetOffset() + moveVector );
}
else
{
int diameter = (int) EuclideanNorm( end - center ) * 2;
pad->SetSize( wxSize( diameter, diameter ) );
}
}
break;
case PAD_SHAPE_OVAL:
case PAD_SHAPE_TRAPEZOID:
case PAD_SHAPE_RECT:
case PAD_SHAPE_ROUNDRECT:
case PAD_SHAPE_CHAMFERED_RECT:
{
VECTOR2I topLeft = m_editPoints->Point( RECT_TOP_LEFT ).GetPosition();
VECTOR2I topRight = m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition();
VECTOR2I botLeft = m_editPoints->Point( RECT_BOT_LEFT ).GetPosition();
VECTOR2I botRight = m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition();
pinEditedCorner( getEditedPointIndex(), Mils2iu( 1 ), Mils2iu( 1 ), topLeft, topRight,
botLeft, botRight,pad->GetPosition(), pad->GetDrillSize() );
if( ( pad->GetOffset().x || pad->GetOffset().y )
|| ( pad->GetDrillSize().x && pad->GetDrillSize().y ) )
{
// Keep hole pinned at the current location; adjust the pad around the hole
wxPoint center = pad->GetPosition();
int dist[4];
if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) )
|| isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) )
{
dist[0] = center.x - topLeft.x;
dist[1] = center.y - topLeft.y;
dist[2] = botRight.x - center.x;
dist[3] = botRight.y - center.y;
}
else
{
dist[0] = center.x - botLeft.x;
dist[1] = center.y - topRight.y;
dist[2] = topRight.x - center.x;
dist[3] = botLeft.y - center.y;
}
wxSize padSize( dist[0] + dist[2], dist[1] + dist[3] );
wxPoint deltaOffset( padSize.x / 2 - dist[2], padSize.y / 2 - dist[3] );
if( pad->GetOrientation() == 900 || pad->GetOrientation() == 2700 )
std::swap( padSize.x, padSize.y );
RotatePoint( &deltaOffset, -pad->GetOrientation() );
pad->SetSize( padSize );
pad->SetOffset( -deltaOffset );
}
else
{
// Keep pad position at the center of the pad shape
int left, top, right, bottom;
if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) )
|| isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) )
{
left = topLeft.x;
top = topLeft.y;
right = botRight.x;
bottom = botRight.y;
}
else
{
left = botLeft.x;
top = topRight.y;
right = topRight.x;
bottom = botLeft.y;
}
wxSize padSize( abs( right - left ), abs( bottom - top ) );
if( pad->GetOrientation() == 900 || pad->GetOrientation() == 2700 )
std::swap( padSize.x, padSize.y );
pad->SetSize( padSize );
pad->SetPosition( wxPoint( ( left + right ) / 2, ( top + bottom ) / 2 ) );
}
}
break;
default: // suppress warnings
break;
}
}
break;
case PCB_MODULE_ZONE_AREA_T:
case PCB_ZONE_AREA_T:
{
ZONE_CONTAINER* zone = static_cast<ZONE_CONTAINER*>( item );
zone->ClearFilledPolysList();
SHAPE_POLY_SET& outline = *zone->Outline();
for( int i = 0; i < outline.TotalVertices(); ++i )
{
if( outline.CVertex( i ) != m_editPoints->Point( i ).GetPosition() )
zone->SetNeedRefill( true );
outline.SetVertex( i, m_editPoints->Point( i ).GetPosition() );
}
validatePolygon( outline );
zone->HatchBorder();
break;
}
case PCB_DIMENSION_T:
{
DIMENSION* dimension = static_cast<DIMENSION*>( item );
// Check which point is currently modified and updated dimension's points respectively
if( isModified( m_editPoints->Point( DIM_CROSSBARO ) ) )
{
VECTOR2D featureLine( m_editedPoint->GetPosition() - dimension->GetOrigin() );
VECTOR2D crossBar( dimension->GetEnd() - dimension->GetOrigin() );
if( featureLine.Cross( crossBar ) > 0 )
dimension->SetHeight( -featureLine.EuclideanNorm(), boardSettings.m_DimensionPrecision );
else
dimension->SetHeight( featureLine.EuclideanNorm(), boardSettings.m_DimensionPrecision );
}
else if( isModified( m_editPoints->Point( DIM_CROSSBARF ) ) )
{
VECTOR2D featureLine( m_editedPoint->GetPosition() - dimension->GetEnd() );
VECTOR2D crossBar( dimension->GetEnd() - dimension->GetOrigin() );
if( featureLine.Cross( crossBar ) > 0 )
dimension->SetHeight( -featureLine.EuclideanNorm(), boardSettings.m_DimensionPrecision );
else
dimension->SetHeight( featureLine.EuclideanNorm(), boardSettings.m_DimensionPrecision );
}
else if( isModified( m_editPoints->Point( DIM_FEATUREGO ) ) )
{
dimension->SetOrigin( wxPoint( m_editedPoint->GetPosition().x, m_editedPoint->GetPosition().y ),
boardSettings.m_DimensionPrecision );
m_editPoints->Point( DIM_CROSSBARO ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBARO ),
m_editPoints->Point( DIM_FEATUREGO ) ) );
m_editPoints->Point( DIM_CROSSBARF ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBARF ),
m_editPoints->Point( DIM_FEATUREDO ) ) );
}
else if( isModified( m_editPoints->Point( DIM_FEATUREDO ) ) )
{
dimension->SetEnd( wxPoint( m_editedPoint->GetPosition().x, m_editedPoint->GetPosition().y ) ,
boardSettings.m_DimensionPrecision );
m_editPoints->Point( DIM_CROSSBARO ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBARO ),
m_editPoints->Point( DIM_FEATUREGO ) ) );
m_editPoints->Point( DIM_CROSSBARF ).SetConstraint( new EC_LINE( m_editPoints->Point( DIM_CROSSBARF ),
m_editPoints->Point( DIM_FEATUREDO ) ) );
}
break;
}
default:
break;
}
getView()->Update( item );
}
void POINT_EDITOR::finishItem()
{
auto item = m_editPoints->GetParent();
if( !item )
return;
if( item->Type() == PCB_ZONE_AREA_T || item->Type() == PCB_MODULE_ZONE_AREA_T )
{
auto zone = static_cast<ZONE_CONTAINER*>( item );
if( zone->IsFilled() && m_refill && zone->NeedRefill() )
m_toolMgr->RunAction( PCB_ACTIONS::zoneFill, true, zone );
}
}
bool POINT_EDITOR::validatePolygon( SHAPE_POLY_SET& aPoly ) const
{
bool valid = !aPoly.IsSelfIntersecting();
if( m_statusPopup )
{
if( valid )
{
m_statusPopup->Hide();
}
else
{
wxPoint p = wxGetMousePosition() + wxPoint( 20, 20 );
m_statusPopup->Move( p );
m_statusPopup->PopupFor( 1500 );
}
}
return valid;
}
void POINT_EDITOR::updatePoints()
{
if( !m_editPoints )
return;
EDA_ITEM* item = m_editPoints->GetParent();
if( !item )
return;
switch( item->Type() )
{
case PCB_LINE_T:
case PCB_MODULE_EDGE_T:
{
const DRAWSEGMENT* segment = static_cast<const DRAWSEGMENT*>( item );
switch( segment->GetShape() )
{
case S_SEGMENT:
m_editPoints->Point( SEG_START ).SetPosition( segment->GetStart() );
m_editPoints->Point( SEG_END ).SetPosition( segment->GetEnd() );
break;
case S_RECT:
m_editPoints->Point( RECT_TOP_LEFT ).SetPosition( segment->GetStart() );
m_editPoints->Point( RECT_TOP_RIGHT ).SetPosition( segment->GetEnd().x,
segment->GetStart().y );
m_editPoints->Point( RECT_BOT_RIGHT ).SetPosition( segment->GetEnd() );
m_editPoints->Point( RECT_BOT_LEFT ).SetPosition( segment->GetStart().x,
segment->GetEnd().y );
break;
case S_ARC:
m_editPoints->Point( ARC_CENTER ).SetPosition( segment->GetCenter() );
m_editPoints->Point( ARC_START ).SetPosition( segment->GetArcStart() );
m_editPoints->Point( ARC_MID ).SetPosition( segment->GetArcMid() );
m_editPoints->Point( ARC_END ).SetPosition( segment->GetArcEnd() );
break;
case S_CIRCLE:
m_editPoints->Point( CIRC_CENTER ).SetPosition( segment->GetCenter() );
m_editPoints->Point( CIRC_END ).SetPosition( segment->GetEnd() );
break;
case S_POLYGON:
{
const auto& points = segment->BuildPolyPointsList();
if( m_editPoints->PointsSize() != (unsigned) points.size() )
{
getView()->Remove( m_editPoints.get() );
m_editedPoint = nullptr;
m_editPoints = EDIT_POINTS_FACTORY::Make( item, getView()->GetGAL() );
getView()->Add( m_editPoints.get() );
}
else
{
for( unsigned i = 0; i < points.size(); i++ )
m_editPoints->Point( i ).SetPosition( points[i] );
}
break;
}
case S_CURVE:
m_editPoints->Point( BEZIER_CURVE_START ).SetPosition( segment->GetStart() );
m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT1 ).SetPosition( segment->GetBezControl1() );
m_editPoints->Point( BEZIER_CURVE_CONTROL_POINT2 ).SetPosition( segment->GetBezControl2() );
m_editPoints->Point( BEZIER_CURVE_END ).SetPosition( segment->GetEnd() );
break;
default: // suppress warnings
break;
}
break;
}
case PCB_PAD_T:
{
const D_PAD* pad = static_cast<const D_PAD*>( item );
bool locked = pad->GetParent() && pad->GetParent()->PadsLocked();
wxPoint shapePos = pad->ShapePos();
wxPoint halfSize( pad->GetSize().x / 2, pad->GetSize().y / 2 );
switch( pad->GetShape() )
{
case PAD_SHAPE_CIRCLE:
{
int target = locked ? 0 : 2;
// Careful; pad shape is mutable...
if( int( m_editPoints->PointsSize() ) != target )
{
getView()->Remove( m_editPoints.get() );
m_editedPoint = nullptr;
m_editPoints = EDIT_POINTS_FACTORY::Make( item, getView()->GetGAL() );
getView()->Add( m_editPoints.get() );
}
else if( target == 2 )
{
VECTOR2I vec = m_editPoints->Point( CIRC_END ).GetPosition()
- m_editPoints->Point( CIRC_CENTER ).GetPosition();
vec.Resize( halfSize.x );
m_editPoints->Point( CIRC_CENTER ).SetPosition( shapePos );
m_editPoints->Point( CIRC_END ).SetPosition( vec + shapePos );
}
}
break;
case PAD_SHAPE_OVAL:
case PAD_SHAPE_TRAPEZOID:
case PAD_SHAPE_RECT:
case PAD_SHAPE_ROUNDRECT:
case PAD_SHAPE_CHAMFERED_RECT:
{
// Careful; pad shape and orientation are mutable...
int target = locked || (int) pad->GetOrientation() % 900 > 0 ? 0 : 4;
if( int( m_editPoints->PointsSize() ) != target )
{
getView()->Remove( m_editPoints.get() );
m_editedPoint = nullptr;
m_editPoints = EDIT_POINTS_FACTORY::Make( item, getView()->GetGAL() );
getView()->Add( m_editPoints.get() );
}
else if( target == 4 )
{
if( pad->GetOrientation() == 900 || pad->GetOrientation() == 2700 )
std::swap( halfSize.x, halfSize.y );
m_editPoints->Point( RECT_TOP_LEFT ).SetPosition( shapePos - halfSize );
m_editPoints->Point( RECT_TOP_RIGHT ).SetPosition( wxPoint( shapePos.x + halfSize.x,
shapePos.y - halfSize.y ) );
m_editPoints->Point( RECT_BOT_RIGHT ).SetPosition( shapePos + halfSize );
m_editPoints->Point( RECT_BOT_LEFT ).SetPosition( wxPoint( shapePos.x - halfSize.x,
shapePos.y + halfSize.y ) );
}
}
break;
default: // suppress warnings
break;
}
}
break;
case PCB_MODULE_ZONE_AREA_T:
case PCB_ZONE_AREA_T:
{
ZONE_CONTAINER* zone = static_cast<ZONE_CONTAINER*>( item );
const SHAPE_POLY_SET* outline = zone->Outline();
if( m_editPoints->PointsSize() != (unsigned) outline->TotalVertices() )
{
getView()->Remove( m_editPoints.get() );
m_editedPoint = nullptr;
m_editPoints = EDIT_POINTS_FACTORY::Make( item, getView()->GetGAL() );
getView()->Add( m_editPoints.get() );
}
else
{
for( int i = 0; i < outline->TotalVertices(); ++i )
m_editPoints->Point( i ).SetPosition( outline->CVertex( i ) );
}
break;
}
case PCB_DIMENSION_T:
{
const DIMENSION* dimension = static_cast<const DIMENSION*>( item );
m_editPoints->Point( DIM_CROSSBARO ).SetPosition( dimension->m_crossBarO );
m_editPoints->Point( DIM_CROSSBARF ).SetPosition( dimension->m_crossBarF );
m_editPoints->Point( DIM_FEATUREGO ).SetPosition( dimension->m_featureLineGO );
m_editPoints->Point( DIM_FEATUREDO ).SetPosition( dimension->m_featureLineDO );
break;
}
default:
break;
}
getView()->Update( m_editPoints.get() );
}
void POINT_EDITOR::setEditedPoint( EDIT_POINT* aPoint )
{
KIGFX::VIEW_CONTROLS* controls = getViewControls();
if( aPoint )
{
frame()->GetCanvas()->SetCurrentCursor( wxCURSOR_ARROW );
controls->ForceCursorPosition( true, aPoint->GetPosition() );
controls->ShowCursor( true );
}
else
{
if( frame()->ToolStackIsEmpty() )
controls->ShowCursor( false );
controls->ForceCursorPosition( false );
}
m_editedPoint = aPoint;
}
void POINT_EDITOR::setAltConstraint( bool aEnabled )
{
if( aEnabled )
{
EDIT_LINE* line = dynamic_cast<EDIT_LINE*>( m_editedPoint );
bool isPoly = false;
if( m_editPoints->GetParent()->Type() == PCB_ZONE_AREA_T
|| m_editPoints->GetParent()->Type() == PCB_MODULE_ZONE_AREA_T )
isPoly = true;
else if( m_editPoints->GetParent()->Type() == PCB_LINE_T
|| m_editPoints->GetParent()->Type() == PCB_MODULE_EDGE_T )
{
DRAWSEGMENT* seg = static_cast<DRAWSEGMENT*>( m_editPoints->GetParent() );
isPoly = seg->GetShape() == S_POLYGON;
}
if( line && isPoly )
{
m_altConstraint.reset( (EDIT_CONSTRAINT<EDIT_POINT>*)( new EC_CONVERGING( *line, *m_editPoints ) ) );
}
else
{
// Find a proper constraining point for 45 degrees mode
m_altConstrainer = get45DegConstrainer();
m_altConstraint.reset( new EC_45DEGREE( *m_editedPoint, m_altConstrainer ) );
}
}
else
{
m_altConstraint.reset();
}
}
EDIT_POINT POINT_EDITOR::get45DegConstrainer() const
{
EDA_ITEM* item = m_editPoints->GetParent();
switch( item->Type() )
{
case PCB_LINE_T:
case PCB_MODULE_EDGE_T:
{
const DRAWSEGMENT* segment = static_cast<const DRAWSEGMENT*>( item );
{
switch( segment->GetShape() )
{
case S_SEGMENT:
return *( m_editPoints->Next( *m_editedPoint ) ); // select the other end of line
case S_ARC:
case S_CIRCLE:
return m_editPoints->Point( CIRC_CENTER );
default: // suppress warnings
break;
}
}
break;
}
case PCB_DIMENSION_T:
{
// Constraint for crossbar
if( isModified( m_editPoints->Point( DIM_FEATUREGO ) ) )
return m_editPoints->Point( DIM_FEATUREDO );
else if( isModified( m_editPoints->Point( DIM_FEATUREDO ) ) )
return m_editPoints->Point( DIM_FEATUREGO );
else
return EDIT_POINT( m_editedPoint->GetPosition() ); // no constraint
break;
}
default:
break;
}
// In any other case we may align item to its original position
return m_original;
}
bool POINT_EDITOR::canAddCorner( const EDA_ITEM& aItem )
{
const auto type = aItem.Type();
// Works only for zones and line segments
return type == PCB_ZONE_AREA_T || type == PCB_MODULE_ZONE_AREA_T ||
( ( type == PCB_LINE_T || type == PCB_MODULE_EDGE_T ) &&
( static_cast<const DRAWSEGMENT&>( aItem ).GetShape() == S_SEGMENT ||
static_cast<const DRAWSEGMENT&>( aItem ).GetShape() == S_POLYGON ) );
}
bool POINT_EDITOR::addCornerCondition( const SELECTION& aSelection )
{
if( aSelection.Size() != 1 )
return false;
const EDA_ITEM* item = aSelection.Front();
return ( item != nullptr ) && canAddCorner( *item );
}
// Finds a corresponding vertex in a polygon set
static std::pair<bool, SHAPE_POLY_SET::VERTEX_INDEX>
findVertex( SHAPE_POLY_SET& aPolySet, const EDIT_POINT& aPoint )
{
for( auto it = aPolySet.IterateWithHoles(); it; ++it )
{
auto vertexIdx = it.GetIndex();
if( aPolySet.CVertex( vertexIdx ) == aPoint.GetPosition() )
return std::make_pair( true, vertexIdx );
}
return std::make_pair( false, SHAPE_POLY_SET::VERTEX_INDEX() );
}
bool POINT_EDITOR::removeCornerCondition( const SELECTION& )
{
if( !m_editPoints || !m_editedPoint )
return false;
EDA_ITEM* item = m_editPoints->GetParent();
if( !item || !( item->Type() == PCB_ZONE_AREA_T || item->Type() == PCB_MODULE_ZONE_AREA_T ||
( ( item->Type() == PCB_MODULE_EDGE_T || item->Type() == PCB_LINE_T ) &&
static_cast<DRAWSEGMENT*>( item )->GetShape() == S_POLYGON ) ) )
return false;
SHAPE_POLY_SET *polyset;
if( item->Type() == PCB_ZONE_AREA_T || item->Type() == PCB_MODULE_ZONE_AREA_T )
polyset = static_cast<ZONE_CONTAINER*>( item )->Outline();
else
polyset = &static_cast<DRAWSEGMENT*>( item )->GetPolyShape();
auto vertex = findVertex( *polyset, *m_editedPoint );
if( !vertex.first )
return false;
const auto& vertexIdx = vertex.second;
// Check if there are enough vertices so one can be removed without
// degenerating the polygon.
// The first condition allows one to remove all corners from holes (when
// there are only 2 vertices left, a hole is removed).
if( vertexIdx.m_contour == 0 && polyset->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour].PointCount() <= 3 )
return false;
// Remove corner does not work with lines
if( dynamic_cast<EDIT_LINE*>( m_editedPoint ) )
return false;
return m_editedPoint != NULL;
}
int POINT_EDITOR::addCorner( const TOOL_EVENT& aEvent )
{
if( !m_editPoints )
return 0;
EDA_ITEM* item = m_editPoints->GetParent();
PCB_BASE_EDIT_FRAME* frame = getEditFrame<PCB_BASE_EDIT_FRAME>();
const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
// called without an active edited polygon
if( !item || !canAddCorner( *item ) )
return 0;
DRAWSEGMENT* graphicItem = dynamic_cast<DRAWSEGMENT*>( item );
BOARD_COMMIT commit( frame );
if( item->Type() == PCB_ZONE_AREA_T || item->Type() == PCB_MODULE_ZONE_AREA_T ||
( graphicItem && graphicItem->GetShape() == S_POLYGON ) )
{
unsigned int nearestIdx = 0;
unsigned int nextNearestIdx = 0;
unsigned int nearestDist = INT_MAX;
unsigned int firstPointInContour = 0;
SHAPE_POLY_SET* zoneOutline;
if( item->Type() == PCB_ZONE_AREA_T || item->Type() == PCB_MODULE_ZONE_AREA_T )
{
ZONE_CONTAINER* zone = static_cast<ZONE_CONTAINER*>( item );
zoneOutline = zone->Outline();
zone->SetNeedRefill( true );
}
else
zoneOutline = &( graphicItem->GetPolyShape() );
commit.Modify( item );
// Search the best outline segment to add a new corner
// and therefore break this segment into two segments
// Object to iterate through the corners of the outlines (main contour and its holes)
SHAPE_POLY_SET::ITERATOR iterator = zoneOutline->Iterate( 0,
zoneOutline->OutlineCount()-1, /* IterateHoles */ true );
int curr_idx = 0;
// Iterate through all the corners of the outlines and search the best segment
for( ; iterator; iterator++, curr_idx++ )
{
int jj = curr_idx+1;
if( iterator.IsEndContour() )
{ // We reach the last point of the current contour (main or hole)
jj = firstPointInContour;
firstPointInContour = curr_idx+1; // Prepare next contour analysis
}
SEG curr_segment( zoneOutline->CVertex( curr_idx ), zoneOutline->CVertex( jj ) );
unsigned int distance = curr_segment.Distance( cursorPos );
if( distance < nearestDist )
{
nearestDist = distance;
nearestIdx = curr_idx;
nextNearestIdx = jj;
}
}
// Find the point on the closest segment
auto& sideOrigin = zoneOutline->CVertex( nearestIdx );
auto& sideEnd = zoneOutline->CVertex( nextNearestIdx );
SEG nearestSide( sideOrigin, sideEnd );
VECTOR2I nearestPoint = nearestSide.NearestPoint( cursorPos );
// Do not add points that have the same coordinates as ones that already belong to polygon
// instead, add a point in the middle of the side
if( nearestPoint == sideOrigin || nearestPoint == sideEnd )
nearestPoint = ( sideOrigin + sideEnd ) / 2;
zoneOutline->InsertVertex( nextNearestIdx, nearestPoint );
// We re-hatch the filled zones but not polygons
if( item->Type() == PCB_ZONE_AREA_T || item->Type() == PCB_MODULE_ZONE_AREA_T )
static_cast<ZONE_CONTAINER*>( item )->HatchBorder();
commit.Push( _( "Add a zone corner" ) );
}
else if( graphicItem && graphicItem->GetShape() == S_SEGMENT )
{
commit.Modify( graphicItem );
SEG seg( graphicItem->GetStart(), graphicItem->GetEnd() );
VECTOR2I nearestPoint = seg.NearestPoint( cursorPos );
// Move the end of the line to the break point..
graphicItem->SetEnd( wxPoint( nearestPoint.x, nearestPoint.y ) );
if( graphicItem->Type() == PCB_MODULE_EDGE_T )
static_cast<EDGE_MODULE*>( graphicItem )->SetLocalCoord();
// and add another one starting from the break point
DRAWSEGMENT* newSegment;
if( item->Type() == PCB_MODULE_EDGE_T )
{
EDGE_MODULE* edge = static_cast<EDGE_MODULE*>( graphicItem );
assert( edge->GetParent()->Type() == PCB_MODULE_T );
newSegment = new EDGE_MODULE( *edge );
}
else
{
newSegment = new DRAWSEGMENT( *graphicItem );
}
newSegment->ClearSelected();
newSegment->SetStart( wxPoint( nearestPoint.x, nearestPoint.y ) );
newSegment->SetEnd( wxPoint( seg.B.x, seg.B.y ) );
if( newSegment->Type() == PCB_MODULE_EDGE_T )
static_cast<EDGE_MODULE*>( newSegment )->SetLocalCoord();
commit.Add( newSegment );
commit.Push( _( "Split segment" ) );
}
updatePoints();
return 0;
}
int POINT_EDITOR::removeCorner( const TOOL_EVENT& aEvent )
{
if( !m_editPoints || !m_editedPoint )
return 0;
EDA_ITEM* item = m_editPoints->GetParent();
if( !item )
return 0;
SHAPE_POLY_SET* polygon = nullptr;
if( item->Type() == PCB_ZONE_AREA_T || item->Type() == PCB_MODULE_ZONE_AREA_T )
{
auto zone = static_cast<ZONE_CONTAINER*>( item );
polygon = zone->Outline();
zone->SetNeedRefill( true );
}
else if( (item->Type() == PCB_MODULE_EDGE_T ) || ( item->Type() == PCB_LINE_T ) )
{
auto ds = static_cast<DRAWSEGMENT*>( item );
if( ds->GetShape() == S_POLYGON )
polygon = &ds->GetPolyShape();
}
if( !polygon )
return 0;
PCB_BASE_FRAME* frame = getEditFrame<PCB_BASE_FRAME>();
BOARD_COMMIT commit( frame );
auto vertex = findVertex( *polygon, *m_editedPoint );
if( vertex.first )
{
const auto& vertexIdx = vertex.second;
auto& outline = polygon->Polygon( vertexIdx.m_polygon )[vertexIdx.m_contour];
if( outline.PointCount() > 3 )
{
// the usual case: remove just the corner when there are >3 vertices
commit.Modify( item );
polygon->RemoveVertex( vertexIdx );
validatePolygon( *polygon );
}
else
{
// either remove a hole or the polygon when there are <= 3 corners
if( vertexIdx.m_contour > 0 )
{
// remove hole
commit.Modify( item );
polygon->RemoveContour( vertexIdx.m_contour );
}
else
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
commit.Remove( item );
}
}
setEditedPoint( nullptr );
commit.Push( _( "Remove a zone/polygon corner" ) );
// Refresh zone hatching
if( item->Type() == PCB_ZONE_AREA_T || item->Type() == PCB_MODULE_ZONE_AREA_T )
static_cast<ZONE_CONTAINER*>( item )->HatchBorder();
updatePoints();
}
return 0;
}
int POINT_EDITOR::modifiedSelection( const TOOL_EVENT& aEvent )
{
updatePoints();
return 0;
}
void POINT_EDITOR::setTransitions()
{
Go( &POINT_EDITOR::OnSelectionChange, ACTIONS::activatePointEditor.MakeEvent() );
Go( &POINT_EDITOR::addCorner, PCB_ACTIONS::pointEditorAddCorner.MakeEvent() );
Go( &POINT_EDITOR::removeCorner, PCB_ACTIONS::pointEditorRemoveCorner.MakeEvent() );
Go( &POINT_EDITOR::modifiedSelection, EVENTS::SelectedItemsModified );
Go( &POINT_EDITOR::OnSelectionChange, EVENTS::SelectedEvent );
Go( &POINT_EDITOR::OnSelectionChange, EVENTS::UnselectedEvent );
}