1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
/*
VeroRoute - Qt based Veroboard/Perfboard/PCB layout & routing application.
Copyright (C) 2017 Alex Lawrow ( dralx@users.sourceforge.net )
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 3 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, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "Common.h"
#include <QPolygonF>
#include "CurveList.h" // For GPEN
// A helper for calculating separations between tracks
struct MyPointF : public QPointF // A point + the pen radius for drawing it
{
MyPointF(qreal x = 0, qreal y = 0, qreal radius = 0) : QPointF(x,y), m_radius(radius) {}
~MyPointF() {}
MyPointF(const QPointF& p, qreal radius) : QPointF(p), m_radius(radius) {}
MyPointF(const MyPointF& o) : QPointF(o), m_radius(o.m_radius) {}
MyPointF& operator=(const MyPointF& o) { QPointF::operator=(o); m_radius = o.m_radius; return *this; }
qreal m_radius = 0; // Pen radius
};
struct MyPolygonF : public QPolygonF // A polygon + the pen radii for drawing it
{
MyPolygonF() {}
~MyPolygonF() {}
MyPolygonF(const QPolygonF& p, GPEN eTrkPen, GPEN ePadPen, qreal radiusTrk, qreal radiusPad, bool bClosed)
: QPolygonF(p), m_eTrkPen(eTrkPen), m_ePadPen(ePadPen), m_radiusTrk(radiusTrk), m_radiusPad(radiusPad), m_bClosed(bClosed)
{
Process();
}
MyPolygonF(const MyPolygonF& o)
: QPolygonF(o), m_eTrkPen(o.m_eTrkPen), m_ePadPen(o.m_ePadPen), m_radiusTrk(o.m_radiusTrk), m_radiusPad(o.m_radiusPad), m_bClosed(o.m_bClosed)
{
Process();
}
MyPolygonF& operator=(const MyPolygonF& o)
{
QPolygonF::operator=(o);
m_eTrkPen = o.m_eTrkPen;
m_ePadPen = o.m_ePadPen;
m_radiusTrk = o.m_radiusTrk;
m_radiusPad = o.m_radiusPad;
m_bClosed = o.m_bClosed;
Process();
return *this;
}
void flipV(const QPointF& pC) // Flip vertically keeping pC fixed
{
const qreal y0 = 2.0 * pC.y();
for (auto p = begin(); p != end(); ++p)
(*p).setY( y0 - (*p).y() );
}
void flipH(const QPointF& pC) // Flip horizontally keeping pC fixed
{
const qreal x0 = 2.0 * pC.x();
for (auto p = begin(); p != end(); ++p)
(*p).setX( x0 - (*p).x() );
}
void rotateCW(const QPointF& pC) // Rotate CW about pC
{
for (auto p = begin(); p != end(); ++p)
{
const QPointF d = (*p) - pC;
(*p) = pC + QPointF(-d.y(), d.x());
}
}
bool HaveVariTracks() const { return QPolygonF::size() > 1 && m_radiusPad > m_radiusTrk && m_radiusTrk > 0; }
void Process(bool bForce = false) const
{
// Set flags to indicate if points and edges are fat or thin
const int iSize = QPolygonF::size();
if ( !bForce && m_bFatPoint.size() == static_cast<size_t>(iSize) ) return;
m_bFatPoint.clear(); m_bFatPoint.resize(static_cast<size_t>(iSize), false);
m_bFatEdge.clear(); m_bFatEdge.resize(static_cast<size_t>(iSize), false);
if ( !HaveVariTracks() ) return;
for (int i = 0, j = 1, iEnd = m_bClosed ? iSize : (iSize-1); i < iEnd; i++, j++)
{
if ( j == iSize ) j = 0;
const QPointF edge(operator[](i) - operator[](j));
if ( edge.x() == 0.0 || edge.y() == 0.0 ) // If edge is V or H ...
{
const size_t I(static_cast<size_t>(i)), J(static_cast<size_t>(j));
m_bFatPoint[I] = m_bFatPoint[J] = m_bFatEdge[I] = true;
}
}
}
// If both pens are set, then it means we are in fat tracks mode
// and using the pad pen for the HV sections instead of the track pen
GPEN m_eTrkPen = GPEN::NONE; // TRK, TRK_GAP, or NONE
GPEN m_ePadPen = GPEN::NONE; // PAD, PAD_GAP, or NONE
qreal m_radiusTrk = 0; // Trk radius
qreal m_radiusPad = 0; // Pen radius
bool m_bClosed = false; // Flag to indicate closed polygon
// A cache indicating the points and edges that are fat (i.e. have pad radius)
mutable std::vector<bool> m_bFatPoint; //
mutable std::vector<bool> m_bFatEdge; // [i] means edge from point with index i to point with index i+1
};
struct PolygonHelper
{
PolygonHelper() {}
~PolygonHelper() {}
QPolygonF m_pWarn; // Set of warning points
qreal m_Dmin = DBL_MAX; // The closest separation found (units of grid squares)
static inline qreal Length(const QPointF& p)
{
return (p.x() == 0.0) ? fabs(p.y()) : (p.y() == 0.0) ? fabs(p.x()) : sqrt( QPointF::dotProduct(p,p) );
}
inline void CalcSeparation(const MyPointF& X, const MyPointF& Y)
{
const qreal sum = ( X.m_radius + Y.m_radius );
const qreal semi = ( X.m_radius - Y.m_radius ) * 0.5;
Update(X, Y, sum, semi);
}
inline void CalcSeparation(const MyPointF& X, const MyPolygonF& P)
{
if ( P.empty() ) return;
//P.Process(); // Not needed since m_bFatEdge already populated
const bool bVariTracks = P.HaveVariTracks();
const qreal sum = ( X.m_radius + P.m_radiusTrk );
const qreal semi = ( X.m_radius - P.m_radiusTrk ) * 0.5;
const qreal sumHV = bVariTracks ? ( X.m_radius + P.m_radiusPad ) : sum;
const qreal semiHV = bVariTracks ? ( ( X.m_radius - P.m_radiusPad ) * 0.5 ) : semi;
const int iSize = P.size();
if ( iSize == 1 ) return Update(X, P[0], sum, semi);
for (int i = 0, j = 1, iEnd = P.m_bClosed ? iSize : (iSize-1); i < iEnd; i++, j++)
{
const size_t I = static_cast<size_t>(i);
if ( j == iSize ) j = 0;
if ( P.m_bFatEdge[I] ) Update(X, Closest(X, P[i], P[j]), sumHV, semiHV);
else Update(X, Closest(X, P[i], P[j]), sum, semi);
}
}
inline void CalcSeparation(const MyPolygonF& P, const MyPolygonF& Q)
{
if ( P.empty() || Q.empty() ) return;
//P.Process(); Q.Process(); // Not needed since m_bFatPoint already populated
size_t i(0), j(0);
for (const auto& p : P) CalcSeparation(MyPointF(p, P.m_bFatPoint[i++] ? P.m_radiusPad : P.m_radiusTrk), Q);
for (const auto& q : Q) CalcSeparation(MyPointF(q, Q.m_bFatPoint[j++] ? Q.m_radiusPad : Q.m_radiusTrk), P);
}
private:
static inline QPointF Closest(const QPointF& X, const QPointF& A, const QPointF& B) // Closest point to X on line segment A-B
{
if ( A == B ) return A;
const QPointF AB(B - A), AX(X - A);
const qreal lambda = std::max(0.0, std::min(1.0, QPointF::dotProduct(AB,AX) / QPointF::dotProduct(AB,AB)));
return A + (AB * lambda);
}
inline void Update(const QPointF& X, const QPointF& Y, qreal sum, qreal semi) // Sum of radii, and semi-diff of radii
{
const QPointF L(Y - X);
const qreal l = Length(L);
const qreal D = round( std::max(0.0, l - sum) * 1000 ); // Units of 0.1 mil
if ( DBL_MAX - m_Dmin != 0.0 )
{
const int iDelta = static_cast<int>( D - m_Dmin * 1000 ); // Units of 0.1 mil
if ( iDelta > 0 ) return;
if ( iDelta < 0 ) m_pWarn.clear();
}
m_Dmin = D * 0.001; // Units of grid squares (100 mil)
QPointF mid( (X + Y) * 0.5 );
if ( semi != 0.0 && l != 0.0 ) mid += L * ( semi / l );
m_pWarn.push_back( mid );
}
};
|