Loading files
ee84c48d4This 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 "schematicpainter.h"
#include "../../application.h"
#include "../../export/graphicsexportsettings.h"
#include "../../export/graphicspainter.h"
#include "../../library/sym/symbol.h"
#include "../../workspace/theme.h"
#include "../circuit/bus.h"
#include "../circuit/netsignal.h"
#include "items/si_busjunction.h"
#include "items/si_buslabel.h"
#include "items/si_busline.h"
#include "items/si_bussegment.h"
#include "items/si_image.h"
#include "items/si_netlabel.h"
#include "items/si_netline.h"
#include "items/si_netpoint.h"
#include "items/si_netsegment.h"
#include "items/si_polygon.h"
#include "items/si_symbol.h"
#include "items/si_symbolpin.h"
#include "items/si_text.h"
#include "schematic.h"
#include <QtCore>
#include <QtGui>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
SchematicPainter::SchematicPainter(const Schematic& schematic,
QStringList* errors, bool thumbnail) noexcept
: mDefaultFont(Application::getDefaultSansSerifFont()),
mNetLabelFont(Application::getDefaultMonospaceFont()) {
mNetLabelFont.setPixelSize(4);
foreach (const SI_Symbol* symbol, schematic.getSymbols()) {
Symbol sym;
sym.transform = Transform(*symbol);
foreach (const SI_SymbolPin* pin, symbol->getPins()) {
sym.pins.append(Pin{
pin->getLibPin().getPosition(),
pin->getLibPin().getRotation(),
pin->getLibPin().getLength(),
pin->getName(),
pin->getNumbersTruncated(),
pin->getLibPin().getNamePosition(),
pin->getLibPin().getNameRotation(),
pin->getLibPin().getNameHeight(),
pin->getLibPin().getNameAlignment(),
pin->getNumbersPosition(),
pin->getNumbersAlignment(),
});
if (pin->isVisibleJunction() && (!thumbnail)) {
mNetJunctions.append(pin->getPosition());
}
}
for (const Polygon& polygon : symbol->getLibSymbol().getPolygons()) {
sym.polygons.append(polygon);
}
for (const Circle& circle : symbol->getLibSymbol().getCircles()) {
sym.circles.append(circle);
}
if (!thumbnail) {
for (const SI_Text* text : symbol->getTexts()) {
Text copy(text->getTextObj());
copy.setText(text->getText()); // Memorize substituted text.
mTexts.append(copy);
}
}
for (const Image& image : symbol->getLibSymbol().getImages()) {
try {
if (!sym.imageFiles.contains(*image.getFileName())) {
const QByteArray content = symbol->getLibSymbol().getDirectory().read(
*image.getFileName()); // can throw
QString error = "Unknown error.";
if (auto pix =
Image::tryLoad(content, image.getFileExtension(), &error)) {
sym.imageFiles.insert(*image.getFileName(), *pix);
} else {
throw RuntimeError(__FILE__, __LINE__,
QString("Failed to load image '%1': %2")
.arg(*image.getFileName(), error));
}
}
sym.images.append(image);
} catch (const Exception& e) {
if (errors) {
errors->append(e.getMsg());
}
}
}
mSymbols.append(sym);
}
foreach (const SI_Polygon* polygon, schematic.getPolygons()) {
mPolygons.append(polygon->getPolygon());
}
if (!thumbnail) {
foreach (const SI_Text* text, schematic.getTexts()) {
Text copy(text->getTextObj());
copy.setText(text->getText()); // Memorize substituted text.
mTexts.append(copy);
}
}
foreach (const SI_Image* image, schematic.getImages()) {
try {
const Image& img = *image->getImage();
if (!mImageFiles.contains(*img.getFileName())) {
const QByteArray content =
schematic.getDirectory().read(*img.getFileName()); // can throw
QString error = "Unknown error.";
if (auto pix =
Image::tryLoad(content, img.getFileExtension(), &error)) {
mImageFiles.insert(*img.getFileName(), *pix);
} else {
throw RuntimeError(__FILE__, __LINE__,
QString("Failed to load image '%1': %2")
.arg(*img.getFileName(), error));
}
}
mImages.append(img);
} catch (const Exception& e) {
if (errors) {
errors->append(e.getMsg());
}
}
}
foreach (const SI_BusSegment* segment, schematic.getBusSegments()) {
if (!thumbnail) {
for (const SI_BusLabel* l : segment->getLabels()) {
mBusLabels.append(Label{
l->getPosition(),
l->getRotation(),
l->getMirrored(),
*l->getBusSegment().getBus().getName(),
});
}
for (const SI_BusJunction* p : segment->getJunctions()) {
if (p->isVisibleJunction()) {
mBusJunctions.append(p->getPosition());
}
}
}
for (const SI_BusLine* l : segment->getLines()) {
mBusLines.append(Line{l->getP1().getPosition(), l->getP2().getPosition(),
l->getWidth()});
}
}
foreach (const SI_NetSegment* segment, schematic.getNetSegments()) {
if (!thumbnail) {
for (const SI_NetLabel* netlabel : segment->getNetLabels()) {
mNetLabels.append(Label{
netlabel->getPosition(),
netlabel->getRotation(),
netlabel->getMirrored(),
*netlabel->getNetSegment().getNetSignal().getName(),
});
}
for (const SI_NetPoint* netpoint : segment->getNetPoints()) {
if (netpoint->isVisibleJunction()) {
mNetJunctions.append(netpoint->getPosition());
}
}
}
for (const SI_NetLine* netline : segment->getNetLines()) {
mNetLines.append(Line{netline->getP1().getPosition(),
netline->getP2().getPosition(),
netline->getWidth()});
}
}
}
SchematicPainter::~SchematicPainter() noexcept {
}
/*******************************************************************************
* General Methods
******************************************************************************/
void SchematicPainter::paint(
QPainter& painter, const GraphicsExportSettings& settings) const noexcept {
GraphicsPainter p(painter);
p.setMinLineWidth(settings.getMinLineWidth());
// Draw Symbols.
foreach (const Symbol& symbol, mSymbols) {
// Helper to draw grab areas first to make them appearing behind every other
// graphics item. Otherwise they might completely cover (hide) other items.
// Images shall be drawn in front of filled polygons/circles, but behind
// non-filled polygons/circles.
enum class ShapeType { GrabArea, Filled, Others };
auto doDraw = [](ShapeType type, bool grabArea, bool filled) {
if (type == ShapeType::GrabArea) {
return grabArea && (!filled);
} else if (type == ShapeType::Filled) {
return filled;
} else {
return (!grabArea) && (!filled);
}
};
auto drawShapes = [&symbol, &settings, &p, &doDraw](ShapeType type) {
// Draw Polygons.
foreach (const Polygon& polygon, symbol.polygons) {
if (doDraw(type, polygon.isGrabArea(), polygon.isFilled())) {
const QString color = polygon.getLayer().getThemeColor();
p.drawPolygon(symbol.transform.map(polygon.getPath()),
*polygon.getLineWidth(), settings.getColor(color),
settings.getFillColor(color, polygon.isFilled(),
polygon.isGrabArea()));
}
}
// Draw Circles.
foreach (const Circle& circle, symbol.circles) {
if (doDraw(type, circle.isGrabArea(), circle.isFilled())) {
const QString color = circle.getLayer().getThemeColor();
p.drawCircle(symbol.transform.map(circle.getCenter()),
*circle.getDiameter(), *circle.getLineWidth(),
settings.getColor(color),
settings.getFillColor(color, circle.isFilled(),
circle.isGrabArea()));
}
}
};
// Draw grab-area shapes.
drawShapes(ShapeType::GrabArea);
// Draw filled shaped.
drawShapes(ShapeType::Filled);
// Draw images.
// Note that if the symbol is mirrored, we do not mirror the image but
// just fit it into its mirrored bounding box because most images are
// not mirrorable as they might contain text. Most symbol images will
// probably be company logos (or other logos) anyway which are not
// intended to be mirrored.
foreach (const Image& image, symbol.images) {
Point pos = symbol.transform.map(image.getPosition());
Angle rot = symbol.transform.mapNonMirrorable(image.getRotation());
if (symbol.transform.getMirrored()) {
pos +=
Point(-image.getWidth(), 0)
.rotated(-image.getRotation() + symbol.transform.getRotation());
rot += Angle::deg180();
}
p.drawImage(pos, rot,
settings.convertImageColors(
symbol.imageFiles.value(*image.getFileName())),
image.getWidth(), image.getHeight(), image.getBorderWidth(),
settings.getColor(Theme::Color::sSchematicImageBorders));
}
// Draw line shapes (no fill, no grab area).
drawShapes(ShapeType::Others);
// Draw Symbol Pins.
foreach (const Pin& pin, symbol.pins) {
p.drawSymbolPin(
symbol.transform.map(pin.position),
symbol.transform.mapNonMirrorable(pin.rotation), *pin.length,
settings.getColor(Theme::Color::sSchematicPinLines), QColor());
Alignment nameAlignment = pin.nameAlignment;
if (symbol.transform.getMirrored()) {
nameAlignment.mirrorV();
}
p.drawText(
symbol.transform.map(pin.position +
pin.namePosition.rotated(pin.rotation)),
symbol.transform.mapNonMirrorable(pin.rotation + pin.nameRotation),
*pin.nameHeight, nameAlignment, pin.name, mDefaultFont,
settings.getColor(Theme::Color::sSchematicPinNames), true, false,
true);
const Angle numberRot = symbol.transform.mapNonMirrorable(pin.rotation);
p.drawText(symbol.transform.map(
pin.position + pin.numbersPosition.rotated(pin.rotation)),
numberRot, *SymbolPin::getNumbersHeight(),
pin.numbersAlignment, pin.numbers, mDefaultFont,
settings.getColor(Theme::Color::sSchematicPinNumbers), true,
false, false);
}
}
// Draw Images.
foreach (const Image& image, mImages) {
p.drawImage(
image.getPosition(), image.getRotation(),
settings.convertImageColors(mImageFiles.value(*image.getFileName())),
image.getWidth(), image.getHeight(), image.getBorderWidth(),
settings.getColor(Theme::Color::sSchematicImageBorders));
}
// Draw Polygons.
foreach (const Polygon& polygon, mPolygons) {
const QString color = polygon.getLayer().getThemeColor();
p.drawPolygon(
polygon.getPath(), *polygon.getLineWidth(), settings.getColor(color),
settings.getFillColor(color, polygon.isFilled(), polygon.isGrabArea()));
}
// Draw Texts.
foreach (const Text& text, mTexts) {
const QString color = text.getLayer().getThemeColor();
p.drawText(text.getPosition(), text.getRotation(), *text.getHeight(),
text.getAlign(), text.getText(), mDefaultFont,
settings.getColor(color), true, false, false);
}
// Draw Net Lines.
foreach (const Line& netline, mNetLines) {
p.drawLine(netline.startPosition, netline.endPosition, *netline.width,
settings.getColor(Theme::Color::sSchematicWires));
}
// Draw Net Junctions.
foreach (const Point& pos, mNetJunctions) {
p.drawNetJunction(pos, settings.getColor(Theme::Color::sSchematicWires));
}
// Draw Net Labels.
foreach (const Label& netlabel, mNetLabels) {
p.drawNetLabel(netlabel.position, netlabel.rotation, netlabel.mirrored,
netlabel.text, mNetLabelFont,
settings.getColor(Theme::Color::sSchematicNetLabels));
}
// Draw Bus Lines.
foreach (const Line& netline, mBusLines) {
p.drawLine(netline.startPosition, netline.endPosition, *netline.width,
settings.getColor(Theme::Color::sSchematicBuses));
}
// Draw Bus Junctions.
foreach (const Point& pos, mBusJunctions) {
p.drawNetJunction(pos, settings.getColor(Theme::Color::sSchematicBuses));
}
// Draw Bus Labels.
foreach (const Label& netlabel, mBusLabels) {
p.drawNetLabel(netlabel.position, netlabel.rotation, netlabel.mirrored,
netlabel.text, mNetLabelFont,
settings.getColor(Theme::Color::sSchematicBusLabels));
}
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace librepcb