kicad/pcbnew/tools/point_editor.cpp

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 );
}