valentina/src/libs/vlayout/vlayoutgenerator.cpp
2023-11-28 16:40:27 +02:00

983 lines
30 KiB
C++

/************************************************************************
**
** @file vlayoutgenerator.cpp
** @author Roman Telezhynskyi <dismine(at)gmail.com>
** @date 2 1, 2015
**
** @brief
** @copyright
** This source code is part of the Valentina project, a pattern making
** program, whose allow create and modeling patterns of clothing.
** Copyright (C) 2013-2015 Valentina project
** <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
**
** Valentina 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.
**
** Valentina 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 Valentina. If not, see <http://www.gnu.org/licenses/>.
**
*************************************************************************/
#include "vlayoutgenerator.h"
#include <QElapsedTimer>
#include <QGraphicsRectItem>
#include <QRectF>
#include <QThreadPool>
#include <QtMath>
#include "../ifc/exception/vexceptionterminatedposition.h"
#include "../vmisc/compatibility.h"
#include "../vmisc/def.h"
#include "vlayoutpaper.h"
#include "vlayoutpiece.h"
//---------------------------------------------------------------------------------------------------------------------
VLayoutGenerator::VLayoutGenerator(QObject *parent)
: QObject(parent)
{
}
//---------------------------------------------------------------------------------------------------------------------
VLayoutGenerator::~VLayoutGenerator()
{
delete bank;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetDetails(const QVector<VLayoutPiece> &details)
{
bank->SetDetails(details);
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetLayoutWidth(qreal width)
{
bank->SetLayoutWidth(width);
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetCaseType(Cases caseType)
{
bank->SetCaseType(caseType);
}
//---------------------------------------------------------------------------------------------------------------------
// cppcheck-suppress unusedFunction
auto VLayoutGenerator::DetailsCount() -> vsizetype
{
return bank->AllDetailsCount();
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::Generate(const QElapsedTimer &timer, qint64 timeout, LayoutErrors previousState)
{
auto HasExpired = [this, timer, timeout]()
{
if (timer.hasExpired(timeout))
{
Timeout();
return true;
}
return false;
};
if (HasExpired())
{
return;
}
if (state != LayoutErrors::Timeout)
{
stopGeneration.store(false);
}
papers.clear();
bank->Reset();
state = previousState;
int width = PageWidth();
int height = PageHeight();
if (VFuzzyComparePossibleNulls(shift, -1))
{
if (bank->PrepareDetails(togetherWithNotches))
{
SetShift(ToPixel(1, Unit::Cm));
}
else
{
state = LayoutErrors::PrepareLayoutError;
return;
}
if (stripOptimization)
{
const qreal b = bank->GetBiggestDiagonal() * multiplier + bank->GetLayoutWidth();
auto SetStrip = [this, b](int &side)
{
if (side >= b * 2)
{
stripOptimizationEnabled = true;
side = qFloor(side / qFloor(side / b));
}
};
IsPortrait() ? SetStrip(height) : SetStrip(width);
}
}
if (HasExpired())
{
return;
}
if (bank->PrepareUnsorted())
{
if (HasExpired())
{
return;
}
while (bank->AllDetailsCount() > 0)
{
if (stopGeneration.load())
{
return;
}
if (HasExpired())
{
return;
}
VLayoutPaper paper(height, width, bank->GetLayoutWidth());
paper.SetShift(shift);
paper.SetPaperIndex(static_cast<quint32>(papers.count()));
paper.SetRotate(rotate);
paper.SetFollowGrainline(followGrainline);
paper.SetRotationNumber(rotationNumber);
paper.SetSaveLength(saveLength);
paper.SetOriginPaperPortrait(IsPortrait());
do
{
const int index = bank->GetNext();
try
{
if (paper.ArrangeDetail(bank->GetDetail(index), stopGeneration))
{
bank->Arranged(index);
}
else
{
bank->NotArranged(index);
}
}
catch (const VExceptionTerminatedPosition &e)
{
qCritical() << e.ErrorMessage();
state = LayoutErrors::TerminatedByException;
return;
}
if (stopGeneration.load())
{
break;
}
if (HasExpired())
{
return;
}
} while (bank->LeftToArrange() > 0);
if (stopGeneration.load())
{
return;
}
if (HasExpired())
{
return;
}
if (paper.Count() > 0)
{
papers.append(paper);
}
else
{
state = LayoutErrors::EmptyPaperError;
return;
}
}
}
else
{
state = LayoutErrors::PrepareLayoutError;
return;
}
if (HasExpired())
{
return;
}
if (stripOptimizationEnabled)
{
GatherPages();
}
if (autoCropWidth)
{
OptimizeWidth();
}
if (IsUnitePages())
{
UnitePages();
}
if (bank->FailedToArrange() == 0)
{
state = LayoutErrors::NoError;
}
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::LayoutEfficiency() const -> qreal
{
qreal efficiency = 0;
if (not papers.isEmpty())
{
efficiency = MasterPage().Efficiency();
}
return efficiency;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::State() const -> LayoutErrors
{
return state;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::PapersCount() const -> vsizetype
{
return papers.size();
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetPapersItems() const -> QList<QGraphicsItem *>
{
QList<QGraphicsItem *> list;
list.reserve(papers.count());
for (const auto &paper : papers)
{
list.append(
paper.GetPaperItem(autoCropLength, autoCropWidth, textAsPaths, togetherWithNotches, showLayoutAllowance));
}
return list;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetGlobalContours() const -> QList<QGraphicsItem *>
{
QList<QGraphicsItem *> list;
list.reserve(papers.count());
for (const auto &paper : papers)
{
list.append(paper.GetGlobalContour());
}
return list;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetAllDetailsItems() const -> QList<QList<QGraphicsItem *>>
{
QList<QList<QGraphicsItem *>> list;
list.reserve(papers.count());
for (const auto &paper : papers)
{
list.append(paper.GetItemDetails(textAsPaths, togetherWithNotches, showLayoutAllowance));
}
return list;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetAllDetails() const -> QVector<QVector<VLayoutPiece>>
{
QVector<QVector<VLayoutPiece>> list;
list.reserve(papers.count());
for (const auto &paper : papers)
{
list.append(paper.GetDetails());
}
return list;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::Abort()
{
stopGeneration.store(true);
state = LayoutErrors::ProcessStoped;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::Timeout()
{
stopGeneration.store(true);
if (state == LayoutErrors::NoError)
{
state = LayoutErrors::Timeout;
}
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsStripOptimization() const -> bool
{
return stripOptimization;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetStripOptimization(bool value)
{
stripOptimization = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsTestAsPaths() const -> bool
{
return textAsPaths;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetTextAsPaths(bool value)
{
textAsPaths = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsBoundaryTogetherWithNotches() const -> bool
{
return togetherWithNotches;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetBoundaryTogetherWithNotches(bool value)
{
togetherWithNotches = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsShowLayoutAllowance() const -> bool
{
return showLayoutAllowance;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetShowLayoutAllowance(bool value)
{
showLayoutAllowance = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsRotationNeeded() const -> bool
{
if (followGrainline)
{
return bank->IsRotationNeeded();
}
return true;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetMultiplier() const -> quint8
{
return multiplier;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetMultiplier(quint8 value)
{
if (value > 10)
{
multiplier = 10;
}
else if (value == 0)
{
multiplier = 1;
}
else
{
multiplier = value;
}
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::PageHeight() const -> int
{
return static_cast<int>(paperHeight - (margins.top() + margins.bottom()));
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::PageWidth() const -> int
{
return static_cast<int>(paperWidth - (margins.left() + margins.right()));
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsPortrait() const -> bool
{
return PageHeight() >= PageWidth();
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::GatherPages()
{
QList<QList<VLayoutPiece>> nDetails;
qreal length = 0;
int j = 0; // papers count
for (int i = 0; i < papers.size(); ++i)
{
const QRectF rec = papers.at(i).DetailsBoundingRect();
if (IsPortrait())
{
int paperHeight = qRound(rec.y() + rec.height());
if (i != papers.size() - 1)
{
paperHeight += qRound(bank->GetLayoutWidth() * 2);
}
if (length + paperHeight <= PageHeight())
{
UniteDetails(j, nDetails, length, i);
length += paperHeight;
}
else
{
length = 0; // Start new paper
++j; // New paper
UniteDetails(j, nDetails, length, i);
length += paperHeight;
}
}
else
{
int paperWidth = qRound(rec.x() + rec.width());
if (i != papers.size() - 1)
{
paperWidth += qRound(bank->GetLayoutWidth() * 2);
}
if (length + paperWidth <= PageWidth())
{
UniteDetails(j, nDetails, length, i);
length += paperWidth;
}
else
{
length = 0; // Start new paper
++j; // New paper
UniteDetails(j, nDetails, length, i);
length += paperWidth;
}
}
}
QVector<VLayoutPaper> nPapers;
nPapers.reserve(nDetails.size());
for (int i = 0; i < nDetails.size(); ++i)
{
VLayoutPaper paper(PageHeight(), PageWidth(), bank->GetLayoutWidth());
paper.SetShift(shift);
paper.SetPaperIndex(static_cast<quint32>(i));
paper.SetRotate(rotate);
paper.SetFollowGrainline(followGrainline);
paper.SetRotationNumber(rotationNumber);
paper.SetSaveLength(saveLength);
paper.SetDetails(nDetails.at(i));
nPapers.append(paper);
}
papers.clear();
papers = nPapers;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::OptimizeWidth()
{
QVector<VLayoutPiece> newDetails;
for (auto &paper : papers)
{
const QRectF rec = paper.DetailsBoundingRect();
QVector<VLayoutPiece> details = paper.GetDetails();
newDetails.resize(0); // resize(0) preserves capacity, unlike QVector::clear()
newDetails.reserve(details.size());
for (auto &d : details)
{
IsPortrait() ? d.Translate(-rec.x() + 1, 0) : d.Translate(0, -rec.y() + 1);
newDetails.append(d);
}
paper.SetDetails(newDetails);
}
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::UnitePages()
{
if (papers.size() < 2)
{
return;
}
QList<qreal> papersLength;
QList<QList<VLayoutPiece>> nDetails;
qreal length = 0;
int j = 0; // papers count
for (int i = 0; i < papers.size(); ++i)
{
if (IsPortrait())
{
int paperHeight = 0;
if (autoCropLength)
{
const QRectF rec = papers.at(i).DetailsBoundingRect();
paperHeight = qRound(rec.y() + rec.height());
}
else
{
paperHeight = papers.at(i).GetHeight();
}
if (i != papers.size() - 1)
{
paperHeight = qRound(paperHeight + bank->GetLayoutWidth() * 2);
}
if (length + paperHeight <= QIMAGE_MAX)
{
UniteDetails(j, nDetails, length, i);
length += paperHeight;
UnitePapers(j, papersLength, length);
}
else
{
length = 0; // Start new paper
++j; // New paper
UniteDetails(j, nDetails, length, i);
length += paperHeight;
UnitePapers(j, papersLength, length);
}
}
else
{
int paperWidth = 0;
if (autoCropLength)
{
const QRectF rec = papers.at(i).DetailsBoundingRect();
paperWidth = qRound(rec.x() + rec.width());
}
else
{
paperWidth = papers.at(i).GetWidth();
}
if (i != papers.size() - 1)
{
paperWidth = qRound(paperWidth + bank->GetLayoutWidth() * 2);
}
if (length + paperWidth <= QIMAGE_MAX)
{
UniteDetails(j, nDetails, length, i);
length += paperWidth;
UnitePapers(j, papersLength, length);
}
else
{
length = 0; // Start new paper
++j; // New paper
UniteDetails(j, nDetails, length, i);
length += paperWidth;
UnitePapers(j, papersLength, length);
}
}
}
QVector<VLayoutPaper> nPapers;
nPapers.reserve(nDetails.size());
for (int i = 0; i < nDetails.size(); ++i)
{
const int height = IsPortrait() ? qRound(papersLength.at(i) + accuracyPointOnLine * 4) : PageHeight();
const int width = IsPortrait() ? PageWidth() : qRound(papersLength.at(i) + accuracyPointOnLine * 4);
VLayoutPaper paper(height, width, bank->GetLayoutWidth());
paper.SetShift(shift);
paper.SetPaperIndex(static_cast<quint32>(i));
paper.SetRotate(rotate);
paper.SetFollowGrainline(followGrainline);
paper.SetRotationNumber(rotationNumber);
paper.SetSaveLength(saveLength);
paper.SetDetails(nDetails.at(i));
nPapers.append(paper);
}
papers.clear();
papers = nPapers;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::UniteDetails(int j, QList<QList<VLayoutPiece>> &nDetails, qreal length, int i) const
{
if ((j == 0 && nDetails.isEmpty()) || j >= nDetails.size())
{ // First or new details in paper
nDetails.insert(j, MoveDetails(length, papers.at(i).GetDetails()));
}
else
{
nDetails[j].append(MoveDetails(length, papers.at(i).GetDetails()));
}
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::UnitePapers(int j, QList<qreal> &papersLength, qreal length)
{
if ((j == 0 && papersLength.isEmpty()) || j >= papersLength.size())
{
papersLength.insert(j, length);
}
else
{
papersLength[j] = length;
}
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::MoveDetails(qreal length, const QVector<VLayoutPiece> &details) const -> QList<VLayoutPiece>
{
if (qFuzzyIsNull(length))
{
return ConvertToList(details);
}
QList<VLayoutPiece> newDetails;
for (auto d : details)
{
IsPortrait() ? d.Translate(0, length) : d.Translate(length, 0);
newDetails.append(d);
}
return newDetails;
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief MasterPage return one "master" page combined all pieces on all pages.
*
* The main purpose of this method is to return the master page. This way we can efficiently calculate the efficiency
* of a solution taking into account empty space between bounding rectangles.
*
* @return master page
*/
auto VLayoutGenerator::MasterPage() const -> VLayoutPaper
{
if (papers.size() < 2)
{
return papers.constFirst();
}
QList<QList<VLayoutPiece>> nDetails;
qreal length = 0;
const int j = 0; // papers count. Always 1.
for (int i = 0; i < papers.size(); ++i)
{
if (IsPortrait())
{
int paperHeight = 0;
if (autoCropLength)
{
const QRectF rec = papers.at(i).DetailsBoundingRect();
paperHeight = qRound(rec.y() + rec.height());
}
else
{
paperHeight = papers.at(i).GetHeight();
}
if (i != papers.size() - 1)
{
paperHeight = qRound(paperHeight + bank->GetLayoutWidth() * 2);
}
UniteDetails(j, nDetails, length, i);
length += paperHeight;
}
else
{
int paperWidth = 0;
if (autoCropLength)
{
const QRectF rec = papers.at(i).DetailsBoundingRect();
paperWidth = qRound(rec.x() + rec.width());
}
else
{
paperWidth = papers.at(i).GetWidth();
}
if (i != papers.size() - 1)
{
paperWidth = qRound(paperWidth + bank->GetLayoutWidth() * 2);
}
UniteDetails(j, nDetails, length, i);
length += paperWidth;
}
}
const int height = IsPortrait() ? qRound(length + accuracyPointOnLine * 4) : PageHeight();
const int width = IsPortrait() ? PageWidth() : qRound(length + accuracyPointOnLine * 4);
VLayoutPaper paper(height, width, bank->GetLayoutWidth());
paper.SetShift(shift);
paper.SetPaperIndex(static_cast<quint32>(0));
paper.SetRotate(rotate);
paper.SetFollowGrainline(followGrainline);
paper.SetRotationNumber(rotationNumber);
paper.SetSaveLength(saveLength);
paper.SetDetails(nDetails.at(0));
return paper;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsUnitePages() const -> bool
{
return unitePages;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetUnitePages(bool value)
{
unitePages = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsSaveLength() const -> bool
{
return saveLength;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetSaveLength(bool value)
{
saveLength = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsPreferOneSheetSolution() const -> bool
{
return preferOneSheetSolution;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetPreferOneSheetSolution(bool value)
{
preferOneSheetSolution = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetAutoCropLength() const -> bool
{
return autoCropLength;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetAutoCropLength(bool value)
{
autoCropLength = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetAutoCropWidth() const -> bool
{
return autoCropWidth;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetAutoCropWidth(bool value)
{
autoCropWidth = value;
}
//---------------------------------------------------------------------------------------------------------------------
// cppcheck-suppress unusedFunction
auto VLayoutGenerator::GetRotationNumber() const -> int
{
return rotationNumber;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetRotationNumber(int value)
{
rotationNumber = value;
if (rotationNumber > 360 || rotationNumber < 1)
{
rotationNumber = 2;
}
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetRotate() const -> bool
{
return rotate;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetRotate(bool value)
{
rotate = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetFollowGrainline() const -> bool
{
return followGrainline;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetFollowGrainline(bool value)
{
followGrainline = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetManualPriority() const -> bool
{
return bank->GetManualPriority();
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetManualPriority(bool value)
{
bank->SetManualPriority(value);
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsNestQuantity() const -> bool
{
return bank->IsNestQuantity();
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetNestQuantity(bool value)
{
bank->SetNestQuantity(value);
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetPaperWidth() const -> qreal
{
return paperWidth;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetPaperWidth(qreal value)
{
paperWidth = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetNestingTime() const -> int
{
return nestingTime;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetNestingTimeMSecs() const -> int
{
return nestingTime * 60000;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetNestingTime(int value)
{
nestingTime = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetEfficiencyCoefficient() const -> qreal
{
return efficiencyCoefficient;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetEfficiencyCoefficient(qreal coefficient)
{
efficiencyCoefficient = coefficient;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::IsUsePrinterFields() const -> bool
{
return usePrinterFields;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetPrinterFields() const -> QMarginsF
{
return margins;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetPrinterFields(bool usePrinterFields, const QMarginsF &value)
{
this->usePrinterFields = usePrinterFields;
margins = value;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetShift() const -> qreal
{
return shift;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetShift(qreal shift)
{
this->shift = shift;
}
//---------------------------------------------------------------------------------------------------------------------
auto VLayoutGenerator::GetPaperHeight() const -> qreal
{
return paperHeight;
}
//---------------------------------------------------------------------------------------------------------------------
void VLayoutGenerator::SetPaperHeight(qreal value)
{
paperHeight = value;
}