/************************************************************************ ** ** @file vboundary.cpp ** @author Roman Telezhynskyi ** @date 24 11, 2023 ** ** @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) 2023 Valentina project ** 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 . ** *************************************************************************/ #include "vboundary.h" #include "../ifc/exception/vexception.h" #include "../vgeometry/vgobject.h" #include "../vmisc/vabstractapplication.h" #include "vlayoutpiecepath.h" #include #if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) #include "../vmisc/compatibility.h" #endif using namespace Qt::Literals::StringLiterals; namespace { //--------------------------------------------------------------------------------------------------------------------- void FillSequance(VBoundarySequenceItemData itemData, QList &sequence) { if (itemData.number <= 0) { itemData.number = 0; sequence.append(itemData); } else if (sequence.isEmpty()) { sequence.append(itemData); } else { for (int i = 0; i < sequence.size(); ++i) { if (sequence.at(i).number > itemData.number || sequence.at(i).number == 0) { sequence.insert(i, itemData); return; } } sequence.append(itemData); } } //--------------------------------------------------------------------------------------------------------------------- auto PrepareSequenceItem(const QVector &path, bool drawMode, VBoundarySequenceItem type) -> VBoundarySequenceItemData { VLayoutPiecePath countur(path); countur.SetCutPath(!drawMode); VBoundarySequenceItemData itemData; itemData.item = QVariant::fromValue(countur); itemData.type = type; return itemData; } //--------------------------------------------------------------------------------------------------------------------- auto PrepareTPassmarkShape(const VLayoutPassmark &passmark, bool drawMode) -> QVector> { QVector shape1; if (passmark.lines.isEmpty()) { return {}; } auto TurnPoint = [](QPointF point) { VLayoutPoint p(point); p.SetTurnPoint(true); return p; }; QLineF line1 = passmark.lines.constFirst(); shape1.append(TurnPoint(line1.p1())); shape1.append(TurnPoint(line1.p2())); if (passmark.lines.size() <= 1) { return {shape1}; } const QLineF &line2 = passmark.lines.constLast(); if (!drawMode) { shape1.append(TurnPoint(line2.p1())); shape1.append(TurnPoint(line2.p2())); shape1.append(TurnPoint(line1.p2())); return {shape1}; } QVector shape2; shape2.append(TurnPoint(line2.p1())); shape2.append(TurnPoint(line2.p2())); return {shape1, shape2}; } //--------------------------------------------------------------------------------------------------------------------- auto PrepareNoneBreakingPassmarkShape(const VLayoutPassmark &passmark) -> QVector> { auto TurnPoint = [](QPointF point) { VLayoutPoint p(point); p.SetTurnPoint(true); return p; }; QVector shape; shape.reserve(passmark.lines.size() + 1); for (int i = 0; i < passmark.lines.size(); ++i) { const QLineF &line = passmark.lines.at(i); shape.append(TurnPoint(line.p1())); if (passmark.lines.size() - 1 == i) { shape.append(TurnPoint(line.p2())); } } return {shape}; } //--------------------------------------------------------------------------------------------------------------------- auto PrepareUPassmarkShape(const VLayoutPassmark &passmark) -> QVector> { auto LayoutPoint = [](QPointF point, bool turnPoint, bool curvePoint) { VLayoutPoint p(point); p.SetTurnPoint(turnPoint); p.SetCurvePoint(curvePoint); return p; }; qreal radius = QLineF(passmark.baseLine.p1(), passmark.lines.constFirst().p1()).length(); if (passmark.baseLine.length() - radius > accuracyPointOnLine) { QVector lines = passmark.lines; if (lines.size() < 3) { return {}; } QLineF line1 = lines.takeFirst(); QVector shape; shape.reserve(4 + passmark.lines.size() + 1); shape.append(LayoutPoint(line1.p1(), true, false)); shape.append(LayoutPoint(line1.p2(), true, true)); QLineF line2 = lines.takeLast(); for (int i = 0; i < passmark.lines.size(); ++i) { const QLineF &line = passmark.lines.at(i); shape.append(LayoutPoint(line.p1(), false, true)); if (passmark.lines.size() - 1 == i) { shape.append(LayoutPoint(line.p2(), false, true)); } } shape.append(LayoutPoint(line2.p1(), true, true)); shape.append(LayoutPoint(line2.p2(), true, false)); return {shape}; } QVector shape; shape.reserve(passmark.lines.size() + 1); for (int i = 0; i < passmark.lines.size(); ++i) { const QLineF &line = passmark.lines.at(i); shape.append(LayoutPoint(line.p1(), false, true)); if (passmark.lines.size() - 1 == i) { shape.append(LayoutPoint(line.p2(), false, true)); } } if (!shape.isEmpty()) { shape.first().SetTurnPoint(true); shape.last().SetTurnPoint(true); } return {shape}; } //--------------------------------------------------------------------------------------------------------------------- auto PreparePassmarkShape(const VLayoutPassmark &passmark, bool drawMode) -> QVector> { switch (passmark.type) { case PassmarkLineType::OneLine: case PassmarkLineType::InternalVMark: case PassmarkLineType::ExternalVMark: case PassmarkLineType::BoxMark: case PassmarkLineType::CheckMark: return PrepareNoneBreakingPassmarkShape(passmark); break; case PassmarkLineType::TMark: return PrepareTPassmarkShape(passmark, drawMode); break; case PassmarkLineType::UMark: return PrepareUPassmarkShape(passmark); break; default: break; } return {}; } //--------------------------------------------------------------------------------------------------------------------- void ConvertTwoLinesPassmark(const VLayoutPassmark &passmark, QList ¬chSequence) { if (!passmark.lines.isEmpty()) { VLayoutPassmark line1 = passmark; line1.lines = {passmark.lines.constFirst()}; line1.type = PassmarkLineType::OneLine; line1.baseLine = passmark.lines.constFirst(); VBoundarySequenceItemData itemData; itemData.item = QVariant::fromValue(line1); itemData.type = VBoundarySequenceItem::Passmark; FillSequance(itemData, notchSequence); } if (passmark.lines.size() > 1) { VLayoutPassmark line2 = passmark; line2.lines = {passmark.lines.constLast()}; line2.type = PassmarkLineType::OneLine; line2.baseLine = passmark.lines.constLast(); VBoundarySequenceItemData itemData; itemData.item = QVariant::fromValue(line2); itemData.type = VBoundarySequenceItem::Passmark; FillSequance(itemData, notchSequence); } } //--------------------------------------------------------------------------------------------------------------------- void ConvertThreeLinesPassmark(const VLayoutPassmark &passmark, QList ¬chSequence) { if (!passmark.lines.isEmpty()) { VLayoutPassmark line1 = passmark; line1.lines = {passmark.lines.constFirst()}; line1.type = PassmarkLineType::OneLine; line1.baseLine = passmark.lines.constFirst(); VBoundarySequenceItemData itemData; itemData.item = QVariant::fromValue(line1); itemData.type = VBoundarySequenceItem::Passmark; FillSequance(itemData, notchSequence); } if (passmark.lines.size() > 1) { VLayoutPassmark line2 = passmark; line2.lines = {passmark.lines.at(1)}; line2.type = PassmarkLineType::OneLine; line2.baseLine = passmark.lines.at(1); VBoundarySequenceItemData itemData; itemData.item = QVariant::fromValue(line2); itemData.type = VBoundarySequenceItem::Passmark; FillSequance(itemData, notchSequence); } if (passmark.lines.size() > 2) { VLayoutPassmark line3 = passmark; line3.lines = {passmark.lines.constLast()}; line3.type = PassmarkLineType::OneLine; line3.baseLine = passmark.lines.constLast(); VBoundarySequenceItemData itemData; itemData.item = QVariant::fromValue(line3); itemData.type = VBoundarySequenceItem::Passmark; FillSequance(itemData, notchSequence); } } //--------------------------------------------------------------------------------------------------------------------- auto SubdividePath(const QVector &boundary, const QPointF &p, QVector &sub1, QVector &sub2) -> bool { if (boundary.size() < 2) { return false; } bool found = false; sub1.clear(); sub2.clear(); auto BreakPoint = [p]() { VLayoutPoint breakPoint(p); breakPoint.SetTurnPoint(true); return breakPoint; }; for (qint32 i = 0; i < boundary.count() - 1; ++i) { if (found) { sub2.append(boundary.at(i)); if (i + 1 == boundary.count() - 1) { sub2.append(boundary.at(i + 1)); } continue; } if (!VGObject::IsPointOnLineSegment(p, static_cast(boundary.at(i)), static_cast(boundary.at(i + 1)))) { sub1.append(boundary.at(i)); continue; } if (not VFuzzyComparePoints(boundary.at(i), p)) { sub1.append(boundary.at(i)); sub1.append(BreakPoint()); } else { if (not sub1.isEmpty()) { sub1.append(BreakPoint()); } } if (not VFuzzyComparePoints(boundary.at(i + 1), p)) { sub2.append(BreakPoint()); if (i + 1 == boundary.count() - 1) { sub2.append(boundary.at(i + 1)); } } found = true; } if (not found) { sub1.clear(); } return found; } //--------------------------------------------------------------------------------------------------------------------- auto InsertDisconnect(QList &sequence, int i, const VBoundarySequenceItemData &item, bool drawMode) -> bool { auto passmark = item.item.value(); bool inserted = false; const auto boundary = sequence.at(i).item.value().Points(); QVector sub1; QVector sub2; if (!SubdividePath(boundary, passmark.baseLine.p1(), sub1, sub2)) { return false; } sequence.removeAt(i); if (not sub2.isEmpty()) { sequence.insert(i, PrepareSequenceItem(sub2, drawMode, VBoundarySequenceItem::Boundary)); } QVector> shape = PreparePassmarkShape(passmark, drawMode); for (auto &subShape : shape) { sequence.insert(i, PrepareSequenceItem(subShape, drawMode, VBoundarySequenceItem::PassmarkShape)); } if (not sub1.isEmpty()) { sequence.insert(i, PrepareSequenceItem(sub1, drawMode, VBoundarySequenceItem::Boundary)); } inserted = true; return inserted; } //--------------------------------------------------------------------------------------------------------------------- auto InsertCutOut(QList &sequence, int i, const VBoundarySequenceItemData &item, bool drawMode) -> bool { auto passmark = item.item.value(); QVector> shape = PreparePassmarkShape(passmark, drawMode); if (shape.isEmpty()) { return false; } const QVector &subShape = shape.constFirst(); if (subShape.size() < 2) { return false; } const auto boundary = sequence.at(i).item.value().Points(); QVector startSub1; QVector startSub2; if (!SubdividePath(boundary, subShape.constFirst(), startSub1, startSub2)) { return false; } QVector endSub1; QVector endSub2; if (!SubdividePath(boundary, subShape.constLast(), endSub1, endSub2)) { return false; } sequence.removeAt(i); if (not endSub2.isEmpty()) { sequence.insert(i, PrepareSequenceItem(endSub2, drawMode, VBoundarySequenceItem::Boundary)); } sequence.insert(i, PrepareSequenceItem(subShape, drawMode, VBoundarySequenceItem::PassmarkShape)); if (not startSub1.isEmpty()) { sequence.insert(i, PrepareSequenceItem(startSub1, drawMode, VBoundarySequenceItem::Boundary)); } return true; } } // namespace //--------------------------------------------------------------------------------------------------------------------- VBoundary::VBoundary(const QVector &boundary, bool seamAllowance, bool builtInSeamAllowance) : m_boundary(boundary), m_seamAllowance(seamAllowance), m_builtInSeamAllowance(builtInSeamAllowance) { } //--------------------------------------------------------------------------------------------------------------------- auto VBoundary::Combine(const QVector &passmarks, bool drawMode, bool layoutAllowance) const -> QList { QList notchSequence; for (const auto &passmark : passmarks) { if (SkipPassmark(passmark, drawMode, layoutAllowance)) { continue; } if (passmark.type == PassmarkLineType::TwoLines) { ConvertTwoLinesPassmark(passmark, notchSequence); continue; } if (passmark.type == PassmarkLineType::ThreeLines) { ConvertThreeLinesPassmark(passmark, notchSequence); continue; } VBoundarySequenceItemData itemData; itemData.item = QVariant::fromValue(passmark); itemData.type = VBoundarySequenceItem::Passmark; FillSequance(itemData, notchSequence); } QList sequence; sequence.append(PrepareSequenceItem(m_boundary, drawMode, VBoundarySequenceItem::Boundary)); for (auto &item : notchSequence) { if (item.type == VBoundarySequenceItem::Passmark) { InsertPassmark(item, sequence, drawMode); } } return sequence; } //--------------------------------------------------------------------------------------------------------------------- auto VBoundary::SkipPassmark(const VLayoutPassmark &passmark, bool drawMode, bool layoutAllowance) const -> bool { if (m_seamAllowance) { if ((m_builtInSeamAllowance && !passmark.isBuiltIn) || (!m_builtInSeamAllowance && passmark.isBuiltIn)) { return true; } } else { if (!passmark.isBuiltIn) { return true; } } if (layoutAllowance && (passmark.type == PassmarkLineType::ExternalVMark || passmark.type == PassmarkLineType::OneLine || passmark.type == PassmarkLineType::TwoLines || passmark.type == PassmarkLineType::ThreeLines || passmark.type == PassmarkLineType::TMark)) { return true; } if (!drawMode && passmark.type == PassmarkLineType::ExternalVMark) { return true; } return false; } //--------------------------------------------------------------------------------------------------------------------- void VBoundary::InsertPassmark(const VBoundarySequenceItemData &item, QList &sequence, bool drawMode) const { auto passmark = item.item.value(); bool inserted = false; for (int i = 0; i < sequence.size(); ++i) { const VBoundarySequenceItemData &itemData = sequence.at(i); if (itemData.type != VBoundarySequenceItem::Boundary) { continue; } switch (passmark.type) { case PassmarkLineType::OneLine: case PassmarkLineType::TMark: case PassmarkLineType::ExternalVMark: inserted = InsertDisconnect(sequence, i, item, drawMode); break; case PassmarkLineType::UMark: case PassmarkLineType::InternalVMark: case PassmarkLineType::BoxMark: case PassmarkLineType::CheckMark: inserted = InsertCutOut(sequence, i, item, drawMode); break; default: break; } if (inserted) { break; } } if (not inserted) { QString pieceName; if (!m_pieceName.isEmpty()) { pieceName = tr("Piece '%1'.").arg(m_pieceName) + ' '_L1; } QString errorMsg; if (!passmark.label.isEmpty()) { errorMsg = pieceName + tr("Unable to insert notch for point '%1'.").arg(passmark.label); } else { errorMsg = pieceName + tr("Unable to insert notch."); } VAbstractApplication::VApp()->IsPedantic() ? throw VException(errorMsg) : qWarning() << VAbstractApplication::warningMessageSignature + errorMsg; } }