KiCad PCB EDA Suite
Loading...
Searching...
No Matches
item_realignment.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "item_realignment.h"
21
22#include <trigo.h>
23
24#include <wx/debug.h>
25
26
27static EDA_ANGLE Snap90Degrees( const EDA_ANGLE& aAngle )
28{
29 const EDA_ANGLE a = aAngle.Normalized();
30
31 if( a < ANGLE_45 )
32 return ANGLE_0;
33 if( a < ANGLE_135 )
34 return ANGLE_90;
35 if( a < EDA_ANGLE{ 225, DEGREES_T } )
36 return ANGLE_180;
37 if( a < EDA_ANGLE( 315, DEGREES_T ) )
38 return ANGLE_270;
39
40 return ANGLE_0;
41}
42
43
44std::optional<ITEM_REALIGNER_BASE::TRANSFORM>
45ORTHO_ITEM_REALIGNER::GetTransform( const std::vector<VECTOR2I>& aPtsA,
46 const std::vector<VECTOR2I>& aPtsB ) const
47{
48 wxCHECK_MSG( aPtsA.size() == aPtsB.size(), std::nullopt, "Point lists must be the same length" );
49
50 const int epsilon = 10;
51
52 std::optional<EDA_ANGLE> bestRotation;
53
54 // First find a good baseline - two matching points in each set that are not conincident
55 for( size_t firstPtIdx = 0; firstPtIdx < aPtsA.size(); firstPtIdx++ )
56 {
57 for( size_t secondPtIdx = firstPtIdx; secondPtIdx < aPtsA.size(); secondPtIdx++ )
58 {
59 const VECTOR2I& ptA1 = aPtsA[firstPtIdx];
60 const VECTOR2I& ptA2 = aPtsA[secondPtIdx];
61 const VECTOR2I candidateBaselineA = ptA2 - ptA1;
62
63 const VECTOR2I& ptB1 = aPtsB[firstPtIdx];
64 const VECTOR2I& ptB2 = aPtsB[secondPtIdx];
65 const VECTOR2I candidateBaselineB = ptB2 - ptB1;
66
67 // If either baseline is too short, it's not a good candidate for alignment
68 if( candidateBaselineA.EuclideanNorm() < epsilon )
69 continue;
70
71 if( candidateBaselineB.EuclideanNorm() < epsilon )
72 continue;
73
74 // Now we have two useful baselines
75 bestRotation = EDA_ANGLE( candidateBaselineB ) - EDA_ANGLE( candidateBaselineA );
76 break;
77 }
78
79 if( bestRotation.has_value() )
80 break;
81 }
82
83 // Failed to find any suitable baseline
84 if( !bestRotation.has_value() )
85 return std::nullopt;
86
87 // Snap the baseline rotation to the nearest 90 degrees, if it's close enough
88 *bestRotation = Snap90Degrees( *bestRotation );
89
90 // Now we can rotate the new points backwards so that it's rotationally aligned to the
91 // existing one.
92 std::vector<VECTOR2I> rotatedPtsB = aPtsB;
93 for( VECTOR2I& pt : rotatedPtsB )
94 {
95 RotatePoint( pt, { 0, 0 }, *bestRotation );
96 }
97
98 // Now we need to find a transform that minimises errors to the pads
99
100 // The first thing to do if find if there is a transform that aligns more than
101 // half of the points. Less that half is no good because that can happen if you widen
102 // a DIP footprint or two-row symbol, for example.
103 // If we find such a case, we lock to the matching points and ignore the outliers
104
105 // Fist find a pad that has the most matches when we fix it
106 std::optional<size_t> bestPadIdx;
107 size_t bestPadMatches = 0;
108
109 for( size_t padIdx = 0; padIdx < aPtsA.size(); padIdx++ )
110 {
111 const VECTOR2I translation = aPtsA[padIdx] - rotatedPtsB[padIdx];
112
113 size_t matches = 0;
114 for( size_t i = 0; i < aPtsA.size(); i++ )
115 {
116 if( ( aPtsA[i] - ( rotatedPtsB[i] + translation ) ).EuclideanNorm() < epsilon )
117 matches++;
118 }
119
120 if( matches > bestPadMatches )
121 {
122 bestPadMatches = matches;
123 bestPadIdx = padIdx;
124 }
125 }
126
127 if( bestPadMatches > aPtsA.size() / 2 )
128 {
129 // We can fix the best point and be reasonably sure it's the right thing to do, so do that.
130 const VECTOR2I translation = aPtsA[*bestPadIdx] - rotatedPtsB[*bestPadIdx];
131 return TRANSFORM{ *bestRotation, translation };
132 }
133
134 // If we can't find a transform that matches more than half the points,
135 // then we can't assume fixing that point is the right thing to do, so
136 // so we'll have to find a transform that minimises the total error instead.
137
138 // Compute the mean displacement between original and rotated points.
139 // This is the least-squares minimising translation.
140 VECTOR2I totalDisplacement = { 0, 0 };
141 for( size_t i = 0; i < aPtsA.size(); i++ )
142 {
143 totalDisplacement += aPtsA[i] - rotatedPtsB[i];
144 }
145
146 VECTOR2I minErrorTranslation = totalDisplacement / static_cast<int>( aPtsA.size() );
147
148 return TRANSFORM{ *bestRotation, minErrorTranslation };
149}
EDA_ANGLE Normalized() const
Definition eda_angle.h:240
std::optional< TRANSFORM > GetTransform(const std::vector< VECTOR2I > &aPtsA, const std::vector< VECTOR2I > &aPtsB) const override
Compute the best fit transform to align the two sets of points.
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:412
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:416
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
static constexpr EDA_ANGLE ANGLE_135
Definition eda_angle.h:414
static EDA_ANGLE Snap90Degrees(const EDA_ANGLE &aAngle)
const double epsilon
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687