Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
94ec4b8feThis file is pinned to the selected release. Download the original to open it in your design tool.
/*
* 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 "footprintgraphicsitem.h"
#include "../../graphics/circlegraphicsitem.h"
#include "../../graphics/holegraphicsitem.h"
#include "../../graphics/polygongraphicsitem.h"
#include "../../graphics/stroketextgraphicsitem.h"
#include "../../graphics/zonegraphicsitem.h"
#include "footprintpadgraphicsitem.h"
#include <librepcb/core/attribute/attributesubstitutor.h>
#include <librepcb/core/library/cmp/component.h>
#include <librepcb/core/types/angle.h>
#include <librepcb/core/types/point.h>
#include <QtCore>
#include <QtWidgets>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
namespace editor {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
FootprintGraphicsItem::FootprintGraphicsItem(
std::shared_ptr<Footprint> footprint, const GraphicsLayerList& layers,
const StrokeFont& font, const PackagePadList* packagePadList,
const Component* component, const QStringList& localeOrder) noexcept
: QGraphicsItemGroup(nullptr),
mFootprint(footprint),
mLayers(layers),
mFont(font),
mPackagePadList(packagePadList),
mComponent(component),
mLocaleOrder(localeOrder),
mOnEditedSlot(*this, &FootprintGraphicsItem::footprintEdited) {
Q_ASSERT(mFootprint);
syncPads();
syncCircles();
syncPolygons();
syncStrokeTexts();
syncZones();
syncHoles();
// Register to the footprint to get notified about any modifications.
mFootprint->onEdited.attach(mOnEditedSlot);
}
FootprintGraphicsItem::~FootprintGraphicsItem() noexcept {
}
/*******************************************************************************
* Getters
******************************************************************************/
QList<std::shared_ptr<FootprintPadGraphicsItem>>
FootprintGraphicsItem::getSelectedPads() noexcept {
QList<std::shared_ptr<FootprintPadGraphicsItem>> pins;
foreach (const auto& ptr, mPadGraphicsItems) {
if (ptr->isSelected()) {
pins.append(ptr);
}
}
return pins;
}
QList<std::shared_ptr<CircleGraphicsItem>>
FootprintGraphicsItem::getSelectedCircles() noexcept {
QList<std::shared_ptr<CircleGraphicsItem>> circles;
foreach (const auto& ptr, mCircleGraphicsItems) {
if (ptr->isSelected()) {
circles.append(ptr);
}
}
return circles;
}
QList<std::shared_ptr<PolygonGraphicsItem>>
FootprintGraphicsItem::getSelectedPolygons() noexcept {
QList<std::shared_ptr<PolygonGraphicsItem>> polygons;
foreach (const auto& ptr, mPolygonGraphicsItems) {
if (ptr->isSelected()) {
polygons.append(ptr);
}
}
return polygons;
}
QList<std::shared_ptr<StrokeTextGraphicsItem>>
FootprintGraphicsItem::getSelectedStrokeTexts() noexcept {
QList<std::shared_ptr<StrokeTextGraphicsItem>> texts;
foreach (const auto& ptr, mStrokeTextGraphicsItems) {
if (ptr->isSelected()) {
texts.append(ptr);
}
}
return texts;
}
QList<std::shared_ptr<ZoneGraphicsItem>>
FootprintGraphicsItem::getSelectedZones() noexcept {
QList<std::shared_ptr<ZoneGraphicsItem>> zones;
foreach (const auto& ptr, mZoneGraphicsItems) {
if (ptr->isSelected()) {
zones.append(ptr);
}
}
return zones;
}
QList<std::shared_ptr<HoleGraphicsItem>>
FootprintGraphicsItem::getSelectedHoles() noexcept {
QList<std::shared_ptr<HoleGraphicsItem>> holes;
foreach (const auto& ptr, mHoleGraphicsItems) {
if (ptr->isSelected()) {
holes.append(ptr);
}
}
return holes;
}
QList<std::shared_ptr<QGraphicsItem>> FootprintGraphicsItem::findItemsAtPos(
const QPainterPath& posAreaSmall, const QPainterPath& posAreaLarge,
FindFlags flags) noexcept {
const QPointF pos = posAreaSmall.boundingRect().center();
// Note: The order of adding the items is very important (the top most item
// must appear as the first item in the list)! For that, we work with
// priorities (0 = highest priority):
//
// 0: holes
// 10: pads tht
// 20: texts board layer
// 30: polygons/circles board layer (±2 for stacking order)
// 110: pads top
// 120: texts top
// 130: polygons/circles top (±2 for stacking order)
// 140: zones top
// 220: texts inner
// 230: polygons/circles inner (±2 for stacking order)
// 240: zones inner
// 310: pads bottom
// 320: texts bottom
// 330: polygons/circles bottom (±2 for stacking order)
// 340: zones bottom
//
// So the system is:
// 0 for holes
// 10 for pads
// 20 for texts
// 30 for polygons/circles
// 40 for zones
// +100 for top layer items
// +200 for inner layer items
// +300 for bottom layer items
//
// And for items not directly under the cursor, but very close to the cursor,
// add +1000.
QMultiMap<std::pair<int, qreal>, std::shared_ptr<QGraphicsItem>> items;
auto priorityFromLayer = [](const Layer& layer) {
if (layer.isTop()) {
return 100;
} else if (layer.isInner()) {
return 200;
} else if (layer.isBottom()) {
return 300;
} else {
return 0;
}
};
auto processItem = [this, &items, &pos, &posAreaSmall, &posAreaLarge, flags](
const std::shared_ptr<QGraphicsItem>& item,
int priority, bool large) {
Q_ASSERT(item);
const QPainterPath grabArea = mapFromItem(item.get(), item->shape());
const QPointF center = grabArea.controlPointRect().center();
const QPointF diff = center - pos;
qreal distance = (diff.x() * diff.x()) + (diff.y() * diff.y());
if (grabArea.contains(pos)) {
items.insert(std::make_pair(priority, distance), item);
} else if ((flags & FindFlag::AcceptNearMatch) &&
grabArea.intersects(large ? posAreaLarge : posAreaSmall)) {
items.insert(std::make_pair(priority + 1000, distance), item);
}
};
if (flags.testFlag(FindFlag::Holes)) {
foreach (auto ptr, mHoleGraphicsItems) {
processItem(std::dynamic_pointer_cast<QGraphicsItem>(ptr), 0, false);
}
}
if (flags.testFlag(FindFlag::Pads)) {
foreach (auto ptr, mPadGraphicsItems) {
int priority = 10;
if (!ptr->getObj().isTht()) {
priority += priorityFromLayer(ptr->getObj().getSmtLayer());
}
processItem(std::dynamic_pointer_cast<QGraphicsItem>(ptr), priority,
false);
}
}
if (flags.testFlag(FindFlag::StrokeTexts)) {
foreach (auto ptr, mStrokeTextGraphicsItems) {
processItem(std::dynamic_pointer_cast<QGraphicsItem>(ptr),
20 + priorityFromLayer(ptr->getObj().getLayer()), false);
}
}
if (flags.testFlag(FindFlag::Circles)) {
foreach (auto ptr, mCircleGraphicsItems) {
int priority = 30 + priorityFromLayer(ptr->getObj().getLayer());
if (ptr->zValue() > 0) priority -= 2;
if (ptr->zValue() < 0) priority += 2;
processItem(std::dynamic_pointer_cast<QGraphicsItem>(ptr), priority,
true); // Probably large grab area makes sense?
}
}
if (flags.testFlag(FindFlag::Polygons)) {
foreach (auto ptr, mPolygonGraphicsItems) {
int priority = 30 + priorityFromLayer(ptr->getObj().getLayer());
if (ptr->zValue() > 0) priority -= 2;
if (ptr->zValue() < 0) priority += 2;
processItem(std::dynamic_pointer_cast<QGraphicsItem>(ptr), priority,
true); // Probably large grab area makes sense?
}
}
if (flags.testFlag(FindFlag::Zones)) {
foreach (auto ptr, mZoneGraphicsItems) {
int priority = 40;
if (ptr->getObj().getLayers().testFlag(Zone::Layer::Top)) {
priority += 100;
} else if (ptr->getObj().getLayers().testFlag(Zone::Layer::Inner)) {
priority += 200;
} else if (ptr->getObj().getLayers().testFlag(Zone::Layer::Bottom)) {
priority += 300;
}
processItem(std::dynamic_pointer_cast<QGraphicsItem>(ptr), priority,
true); // Probably large grab area makes sense?
}
}
return items.values();
}
/*******************************************************************************
* General Methods
******************************************************************************/
void FootprintGraphicsItem::setPosition(const Point& pos) noexcept {
QGraphicsItem::setPos(pos.toPxQPointF());
}
void FootprintGraphicsItem::setRotation(const Angle& rot) noexcept {
QGraphicsItem::setRotation(-rot.toDeg());
}
void FootprintGraphicsItem::updateAllTexts() noexcept {
foreach (const auto& ptr, mPadGraphicsItems) {
ptr->updateText();
}
foreach (const auto& ptr, mStrokeTextGraphicsItems) {
substituteText(*ptr);
}
}
void FootprintGraphicsItem::setSelectionRect(const QRectF rect) noexcept {
QPainterPath path;
path.addRect(rect);
foreach (const auto& ptr, mPadGraphicsItems) {
QPainterPath mappedPath = mapToItem(ptr.get(), path);
ptr->setSelected(ptr->shape().intersects(mappedPath));
}
foreach (const auto& ptr, mCircleGraphicsItems) {
QPainterPath mappedPath = mapToItem(ptr.get(), path);
ptr->setSelected(ptr->shape().intersects(mappedPath));
}
foreach (const auto& ptr, mPolygonGraphicsItems) {
QPainterPath mappedPath = mapToItem(ptr.get(), path);
ptr->setSelected(ptr->shape().intersects(mappedPath));
}
foreach (const auto& ptr, mStrokeTextGraphicsItems) {
QPainterPath mappedPath = mapToItem(ptr.get(), path);
ptr->setSelected(ptr->shape().intersects(mappedPath));
}
foreach (const auto& ptr, mZoneGraphicsItems) {
QPainterPath mappedPath = mapToItem(ptr.get(), path);
ptr->setSelected(ptr->shape().intersects(mappedPath));
}
foreach (const auto& ptr, mHoleGraphicsItems) {
QPainterPath mappedPath = mapToItem(ptr.get(), path);
ptr->setSelected(ptr->shape().intersects(mappedPath));
}
}
/*******************************************************************************
* Private Methods
******************************************************************************/
void FootprintGraphicsItem::syncPads() noexcept {
// Remove obsolete items.
for (auto it = mPadGraphicsItems.begin(); it != mPadGraphicsItems.end();) {
if (!mFootprint->getPads().contains(it.key().get())) {
Q_ASSERT(it.value());
it.value()->setParentItem(nullptr);
it = mPadGraphicsItems.erase(it);
} else {
it++;
}
}
// Add new items.
for (auto it = mFootprint->getPads().begin();
it != mFootprint->getPads().end(); ++it) {
if (!mPadGraphicsItems.contains(it.ptr())) {
auto i = std::make_shared<FootprintPadGraphicsItem>(
it.ptr(), mLayers, mPackagePadList, this);
mPadGraphicsItems.insert(it.ptr(), i);
}
}
}
void FootprintGraphicsItem::syncCircles() noexcept {
// Remove obsolete items.
for (auto it = mCircleGraphicsItems.begin();
it != mCircleGraphicsItems.end();) {
if (!mFootprint->getCircles().contains(it.key().get())) {
Q_ASSERT(it.value());
it.value()->setParentItem(nullptr);
it = mCircleGraphicsItems.erase(it);
} else {
it++;
}
}
// Add new items.
for (auto& obj : mFootprint->getCircles().values()) {
if (!mCircleGraphicsItems.contains(obj)) {
Q_ASSERT(obj);
auto i = std::make_shared<CircleGraphicsItem>(*obj, mLayers, this);
mCircleGraphicsItems.insert(obj, i);
}
}
}
void FootprintGraphicsItem::syncPolygons() noexcept {
// Remove obsolete items.
for (auto it = mPolygonGraphicsItems.begin();
it != mPolygonGraphicsItems.end();) {
if (!mFootprint->getPolygons().contains(it.key().get())) {
Q_ASSERT(it.value());
it.value()->setParentItem(nullptr);
it = mPolygonGraphicsItems.erase(it);
} else {
it++;
}
}
// Add new items.
for (auto& obj : mFootprint->getPolygons().values()) {
if (!mPolygonGraphicsItems.contains(obj)) {
Q_ASSERT(obj);
auto i = std::make_shared<PolygonGraphicsItem>(*obj, mLayers, this);
i->setEditable(true);
mPolygonGraphicsItems.insert(obj, i);
}
}
}
void FootprintGraphicsItem::syncStrokeTexts() noexcept {
// Remove obsolete items.
for (auto it = mStrokeTextGraphicsItems.begin();
it != mStrokeTextGraphicsItems.end();) {
if (!mFootprint->getStrokeTexts().contains(it.key().get())) {
Q_ASSERT(it.key() && it.value());
it.value()->setParentItem(nullptr);
it = mStrokeTextGraphicsItems.erase(it);
} else {
it++;
}
}
// Add new items.
for (auto& obj : mFootprint->getStrokeTexts().values()) {
if (!mStrokeTextGraphicsItems.contains(obj)) {
Q_ASSERT(obj);
auto i =
std::make_shared<StrokeTextGraphicsItem>(*obj, mLayers, mFont, this);
substituteText(*i);
mStrokeTextGraphicsItems.insert(obj, i);
}
}
}
void FootprintGraphicsItem::syncZones() noexcept {
// Remove obsolete items.
for (auto it = mZoneGraphicsItems.begin(); it != mZoneGraphicsItems.end();) {
if (!mFootprint->getZones().contains(it.key().get())) {
Q_ASSERT(it.key() && it.value());
it.value()->setParentItem(nullptr);
it = mZoneGraphicsItems.erase(it);
} else {
it++;
}
}
// Add new items.
for (auto& obj : mFootprint->getZones().values()) {
if (!mZoneGraphicsItems.contains(obj)) {
Q_ASSERT(obj);
auto i = std::make_shared<ZoneGraphicsItem>(*obj, mLayers, this);
i->setEditable(true);
mZoneGraphicsItems.insert(obj, i);
}
}
}
void FootprintGraphicsItem::syncHoles() noexcept {
// Remove obsolete items.
for (auto it = mHoleGraphicsItems.begin(); it != mHoleGraphicsItems.end();) {
if (!mFootprint->getHoles().contains(it.key().get())) {
Q_ASSERT(it.value());
it.value()->setParentItem(nullptr);
it = mHoleGraphicsItems.erase(it);
} else {
it++;
}
}
// Add new items.
for (auto& obj : mFootprint->getHoles().values()) {
if (!mHoleGraphicsItems.contains(obj)) {
Q_ASSERT(obj);
auto i = std::make_shared<HoleGraphicsItem>(*obj, mLayers, true, this);
mHoleGraphicsItems.insert(obj, i);
}
}
}
void FootprintGraphicsItem::footprintEdited(const Footprint& footprint,
Footprint::Event event) noexcept {
Q_UNUSED(footprint);
switch (event) {
case Footprint::Event::PadsEdited:
syncPads();
break;
case Footprint::Event::CirclesEdited:
syncCircles();
break;
case Footprint::Event::PolygonsEdited:
syncPolygons();
break;
case Footprint::Event::StrokeTextsEdited:
syncStrokeTexts();
break;
case Footprint::Event::ZonesEdited:
syncZones();
break;
case Footprint::Event::HolesEdited:
syncHoles();
break;
default:
break;
}
}
void FootprintGraphicsItem::substituteText(
StrokeTextGraphicsItem& text) noexcept {
if (mComponent) {
auto lookup = [this](const QString& key) {
if (key == "COMPONENT") {
return *mComponent->getNames().value(mLocaleOrder);
} else if (key == "NAME") {
return *mComponent->getPrefixes().value(mLocaleOrder) + "?";
} else {
// If an attribute is not defined, return its key. This makes sure that
// e.g. in a schematic frame the texts like "{{FIELD_SHEET}}" are
// visible as "FIELD_SHEET" instead of completely missing text. Same
// applies to the "{{VALUE}}" text - it's almost impossible to
// automatically substitute it by a reasonable value (e.g. the
// component's default value) so let's simply display "VALUE".
return key;
}
};
text.setTextOverride(
AttributeSubstitutor::substitute(text.getObj().getText(), lookup));
}
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace editor
} // namespace librepcb