Loading files
/*
* LibrePCB - Professional EDA for everyone!
* Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
* https://librepcb.org/
*
* 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/>.
*/
/*******************************************************************************
* Includes
******************************************************************************/
#include "path.h"
#include "../utils/toolbox.h"
#include <QtCore>
#include <algorithm>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
Path::Path(const Path& other) noexcept
: mVertices(other.mVertices), mPainterPathPx(other.mPainterPathPx) {
}
Path::Path(const SExpression& node, const Version& fileFormat) {
foreach (const SExpression& child, node.getChildren("vertex")) {
mVertices.append(Vertex(child, fileFormat));
}
}
/*******************************************************************************
* Getters
******************************************************************************/
bool Path::isClosed() const noexcept {
if (mVertices.count() >= 2) {
return (mVertices.first().getPos() == mVertices.last().getPos());
} else {
return false;
}
}
UnsignedLength Path::getTotalStraightLength() const noexcept {
UnsignedLength length(0);
if (mVertices.count() >= 2) {
Point lastPos = mVertices.first().getPos();
for (int i = 1; i < mVertices.count(); ++i) {
const Point& pos = mVertices.at(i).getPos();
length += (pos - lastPos).getLength();
lastPos = pos;
}
}
return length;
}
Point Path::calcNearestPointBetweenVertices(const Point& p) const noexcept {
if (!mVertices.isEmpty()) {
// Note: Does not take arcs into account yet.
Point nearest = mVertices.first().getPos();
for (int i = 1; i < mVertices.count(); ++i) {
Point tmp = Toolbox::nearestPointOnLine(p, mVertices.at(i - 1).getPos(),
mVertices.at(i).getPos());
if ((tmp - p).getLength() < (nearest - p).getLength()) {
nearest = tmp;
}
}
return nearest;
} else {
return Point();
}
}
Path Path::toClosedPath() const noexcept {
Path p(*this);
p.close();
return p;
}
QVector<Path> Path::toOutlineStrokes(const PositiveLength& width) const
noexcept {
QVector<Path> paths;
paths.reserve(mVertices.count());
for (int i = 1; i < mVertices.count(); ++i) { // skip first vertex!
const Vertex& v = mVertices.at(i);
const Vertex& v0 = mVertices.at(i - 1);
if (v0.getAngle() == 0) {
paths.append(obround(v0.getPos(), v.getPos(), width));
} else {
paths.append(arcObround(v0.getPos(), v.getPos(), v0.getAngle(), width));
}
}
return paths;
}
const QPainterPath& Path::toQPainterPathPx() const noexcept {
if (mPainterPathPx.isEmpty()) {
for (int i = 0; i < mVertices.count(); ++i) {
const Vertex& v = mVertices.at(i);
if (i == 0) {
mPainterPathPx.moveTo(v.getPos().toPxQPointF());
continue;
}
const Vertex& v0 = mVertices.at(i - 1);
if (v0.getAngle() == 0) {
mPainterPathPx.lineTo(v.getPos().toPxQPointF());
} else {
QPointF centerPx =
Toolbox::arcCenter(v0.getPos(), v.getPos(), v0.getAngle())
.toPxQPointF();
qreal radiusPx =
Toolbox::arcRadius(v0.getPos(), v.getPos(), v0.getAngle())
.abs()
.toPx();
QPointF diffPx = v0.getPos().toPxQPointF() - centerPx;
qreal startAngleDeg =
-qRadiansToDegrees(qAtan2(diffPx.y(), diffPx.x()));
mPainterPathPx.arcTo(centerPx.x() - radiusPx, centerPx.y() - radiusPx,
radiusPx * 2, radiusPx * 2, startAngleDeg,
v0.getAngle().toDeg());
}
}
}
return mPainterPathPx;
}
/*******************************************************************************
* Transformations
******************************************************************************/
Path& Path::translate(const Point& offset) noexcept {
for (Vertex& vertex : mVertices) {
vertex.setPos(vertex.getPos() + offset);
}
invalidatePainterPath();
return *this;
}
Path Path::translated(const Point& offset) const noexcept {
return Path(*this).translate(offset);
}
Path& Path::mapToGrid(const PositiveLength& gridInterval) noexcept {
for (Vertex& vertex : mVertices) {
vertex.setPos(vertex.getPos().mappedToGrid(gridInterval));
}
invalidatePainterPath();
return *this;
}
Path Path::mappedToGrid(const PositiveLength& gridInterval) const noexcept {
return Path(*this).mapToGrid(gridInterval);
}
Path& Path::rotate(const Angle& angle, const Point& center) noexcept {
for (Vertex& vertex : mVertices) {
vertex.setPos(vertex.getPos().rotated(angle, center));
}
invalidatePainterPath();
return *this;
}
Path Path::rotated(const Angle& angle, const Point& center) const noexcept {
return Path(*this).rotate(angle, center);
}
Path& Path::mirror(Qt::Orientation orientation, const Point& center) noexcept {
for (Vertex& vertex : mVertices) {
vertex.setPos(vertex.getPos().mirrored(orientation, center));
vertex.setAngle(-vertex.getAngle());
}
invalidatePainterPath();
return *this;
}
Path Path::mirrored(Qt::Orientation orientation, const Point& center) const
noexcept {
return Path(*this).mirror(orientation, center);
}
Path& Path::reverse() noexcept {
QVector<Vertex> vertices;
vertices.reserve(mVertices.count());
for (int i = mVertices.count() - 1; i >= 0; --i) {
vertices.append(
Vertex(mVertices.at(i).getPos(), -mVertices.value(i - 1).getAngle()));
}
mVertices = vertices;
invalidatePainterPath();
return *this;
}
Path Path::reversed() const noexcept {
return Path(*this).reverse();
}
/*******************************************************************************
* General Methods
******************************************************************************/
void Path::addVertex(const Vertex& vertex) noexcept {
mVertices.append(vertex);
invalidatePainterPath();
}
void Path::addVertex(const Point& pos, const Angle& angle) noexcept {
addVertex(Vertex(pos, angle));
}
void Path::insertVertex(int index, const Vertex& vertex) noexcept {
mVertices.insert(index, vertex);
invalidatePainterPath();
}
void Path::insertVertex(int index, const Point& pos,
const Angle& angle) noexcept {
insertVertex(index, Vertex(pos, angle));
}
bool Path::close() noexcept {
if (!isClosed() && (mVertices.count() > 1)) {
addVertex(mVertices.first().getPos(), Angle::deg0());
Q_ASSERT(isClosed());
return true;
} else {
return false;
}
}
void Path::serialize(SExpression& root) const {
serializeObjectContainer(root, mVertices, "vertex");
}
/*******************************************************************************
* Operator Overloadings
******************************************************************************/
Path& Path::operator=(const Path& rhs) noexcept {
mVertices = rhs.mVertices;
mPainterPathPx = rhs.mPainterPathPx;
return *this;
}
bool Path::operator<(const Path& rhs) const noexcept {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
return mVertices < rhs.mVertices;
#else
return std::lexicographical_compare(mVertices.begin(), mVertices.end(),
rhs.mVertices.begin(),
rhs.mVertices.end());
#endif
}
/*******************************************************************************
* Static Methods
******************************************************************************/
Path Path::line(const Point& p1, const Point& p2, const Angle& angle) noexcept {
return Path({Vertex(p1, angle), Vertex(p2)});
}
Path Path::circle(const PositiveLength& diameter) noexcept {
return obround(diameter, diameter);
}
Path Path::obround(const PositiveLength& width,
const PositiveLength& height) noexcept {
Path p;
Length rx = width / 2;
Length ry = height / 2;
if (width > height) {
p.addVertex(Point(ry - rx, ry), Angle::deg0());
p.addVertex(Point(rx - ry, ry), -Angle::deg180());
p.addVertex(Point(rx - ry, -ry), Angle::deg0());
p.addVertex(Point(ry - rx, -ry), -Angle::deg180());
p.addVertex(Point(ry - rx, ry), Angle::deg0());
} else if (width < height) {
p.addVertex(Point(rx, ry - rx), Angle::deg0());
p.addVertex(Point(rx, rx - ry), -Angle::deg180());
p.addVertex(Point(-rx, rx - ry), Angle::deg0());
p.addVertex(Point(-rx, ry - rx), -Angle::deg180());
p.addVertex(Point(rx, ry - rx), Angle::deg0());
} else {
Q_ASSERT(width == height);
p.addVertex(Point(rx, 0), -Angle::deg180());
p.addVertex(Point(-rx, 0), -Angle::deg180());
p.addVertex(Point(rx, 0), Angle::deg0());
}
return p;
}
Path Path::obround(const Point& p1, const Point& p2,
const PositiveLength& width) noexcept {
Point diff = p2 - p1;
Path p = obround(UnsignedLength(diff.getLength()) + width, width);
p.rotate(Angle::fromRad(qAtan2(diff.getY().toMm(), diff.getX().toMm())));
p.translate((p1 + p2) / 2);
return p;
}
Path Path::arcObround(const Point& p1, const Point& p2, const Angle& angle,
const PositiveLength& width) noexcept {
if (p1 == p2) {
return circle(width).translated(p1);
}
Length radius = Toolbox::arcRadius(p1, p2, angle).abs();
Length innerRadius = radius - (*width / 2);
Length outerRadius = radius + (*width / 2);
Point center = Toolbox::arcCenter(p1, p2, angle);
Point delta1 = p1 - center;
Point delta2 = p2 - center;
qreal angle1Rad = qAtan2(delta1.getY().toPx(), delta1.getX().toPx());
qreal angle2Rad = qAtan2(delta2.getY().toPx(), delta2.getX().toPx());
Point p1Inner =
center + Point(innerRadius, 0).rotated(Angle::fromRad(angle1Rad));
Point p1Outer =
center + Point(outerRadius, 0).rotated(Angle::fromRad(angle1Rad));
Point p2Inner =
center + Point(innerRadius, 0).rotated(Angle::fromRad(angle2Rad));
Point p2Outer =
center + Point(outerRadius, 0).rotated(Angle::fromRad(angle2Rad));
Path p;
p.addVertex(p1Inner, angle);
p.addVertex(p2Inner, angle < 0 ? Angle::deg180() : -Angle::deg180());
p.addVertex(p2Outer, -angle);
p.addVertex(p1Outer, angle < 0 ? Angle::deg180() : -Angle::deg180());
p.addVertex(p1Inner, Angle::deg0());
return p;
}
Path Path::rect(const Point& p1, const Point& p2) noexcept {
Path p;
p.addVertex(Point(p1.getX(), p1.getY()));
p.addVertex(Point(p2.getX(), p1.getY()));
p.addVertex(Point(p2.getX(), p2.getY()));
p.addVertex(Point(p1.getX(), p2.getY()));
p.addVertex(Point(p1.getX(), p1.getY()));
return p;
}
Path Path::centeredRect(const PositiveLength& width,
const PositiveLength& height) noexcept {
Path p;
Length rx = width / 2;
Length ry = height / 2;
p.addVertex(Point(-rx, ry));
p.addVertex(Point(rx, ry));
p.addVertex(Point(rx, -ry));
p.addVertex(Point(-rx, -ry));
p.addVertex(Point(-rx, ry));
return p;
}
Path Path::octagon(const PositiveLength& width,
const PositiveLength& height) noexcept {
Path p;
Length rx = width / 2;
Length ry = height / 2;
Length a = Length::fromMm(qMin(rx, ry).toMm() * (2 - qSqrt(2)));
p.addVertex(Point(rx, ry - a));
p.addVertex(Point(rx - a, ry));
p.addVertex(Point(a - rx, ry));
p.addVertex(Point(-rx, ry - a));
p.addVertex(Point(-rx, a - ry));
p.addVertex(Point(a - rx, -ry));
p.addVertex(Point(rx - a, -ry));
p.addVertex(Point(rx, a - ry));
p.addVertex(Point(rx, ry - a));
return p;
}
Path Path::flatArc(const Point& p1, const Point& p2, const Angle& angle,
const PositiveLength& maxTolerance) noexcept {
// return straight line if radius is smaller than half of the allowed
// tolerance
Length radiusAbs = Toolbox::arcRadius(p1, p2, angle).abs();
if (radiusAbs <= maxTolerance / 2) {
return line(p1, p2);
}
// calculate how many lines we need to create
qreal radiusAbsNm = static_cast<qreal>(radiusAbs.toNm());
qreal y = qBound(qreal(0.0), static_cast<qreal>(maxTolerance->toNm()),
radiusAbsNm / qreal(4));
qreal stepsPerRad =
qMin(qreal(0.5) / qAcos(1 - y / radiusAbsNm), radiusAbsNm / qreal(2));
int steps = qCeil(stepsPerRad * angle.abs().toRad());
// some other very complex calculations...
qreal angleDelta = angle.toMicroDeg() / (qreal)steps;
Point center = Toolbox::arcCenter(p1, p2, angle);
// create line segments
Path p;
p.addVertex(p1);
for (int i = 1; i < steps; ++i) {
p.addVertex(p1.rotated(Angle(angleDelta * i), center));
}
p.addVertex(p2);
return p;
}
QPainterPath Path::toQPainterPathPx(const QVector<Path>& paths,
bool area) noexcept {
QPainterPath p;
p.setFillRule(Qt::WindingFill);
foreach (const Path& path, paths) {
if (area) {
p |= path.toQPainterPathPx();
} else {
p.addPath(path.toQPainterPathPx());
}
}
return p;
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace librepcb
c136b73e0This file is pinned to the selected release. Download the original to open it in your design tool.