Sticky edges.

This commit is contained in:
Roman Telezhynskyi 2021-08-30 18:45:27 +03:00
parent 3d9a4f6f65
commit b877009d90
10 changed files with 383 additions and 37 deletions

View File

@ -31,6 +31,7 @@
#include "../vmisc/def.h" #include "../vmisc/def.h"
#include "vpsheet.h" #include "vpsheet.h"
#include "vplayout.h"
#include "../vlayout/vtextmanager.h" #include "../vlayout/vtextmanager.h"
#include <QIcon> #include <QIcon>
@ -39,6 +40,86 @@
Q_LOGGING_CATEGORY(pPiece, "p.piece") Q_LOGGING_CATEGORY(pPiece, "p.piece")
namespace
{
constexpr qreal minStickyDistance = ToPixel(3, Unit::Mm);
constexpr qreal maxStickyDistance = ToPixel(10, Unit::Mm);
constexpr qreal stickyShift = ToPixel(1, Unit::Mm);
//---------------------------------------------------------------------------------------------------------------------
auto CutEdge(const QLineF &edge) -> QVector<QPointF>
{
QVector<QPointF> points;
if (qFuzzyIsNull(stickyShift))
{
points.append(edge.p1());
points.append(edge.p2());
}
else
{
const int n = qFloor(edge.length()/stickyShift);
if (n <= 0)
{
points.append(edge.p1());
points.append(edge.p2());
}
else
{
points.reserve(n);
const qreal nShift = edge.length()/n;
for (int i = 1; i <= n+1; ++i)
{
QLineF l1 = edge;
l1.setLength(nShift*(i-1));
points.append(l1.p2());
}
}
}
return points;
}
//---------------------------------------------------------------------------------------------------------------------
auto PrepareStickyPath(const QVector<QPointF> &path) -> QVector<QPointF>
{
if (path.size() < 2)
{
return path;
}
QVector<QPointF> stickyPath;
for (int i=0; i<path.size(); ++i)
{
stickyPath += CutEdge(QLineF(path.at(i), path.at(i < path.size()-1 ? i+1 : 0)));
}
return stickyPath;
}
//---------------------------------------------------------------------------------------------------------------------
auto ClosestDistance(const QVector<QPointF> &path1, const QVector<QPointF> &path2) -> QLineF
{
qreal distance = INT_MAX;
QLineF closestDistance;
for (auto p1 : path1)
{
for (auto p2 : path2)
{
QLineF d(p1, p2);
if (d.length() <= distance)
{
distance = d.length();
closestDistance = d;
}
}
}
return closestDistance;
}
} // namespace
//--------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------
VPPiece::VPPiece(const VLayoutPiece &layoutPiece) VPPiece::VPPiece(const VLayoutPiece &layoutPiece)
: VLayoutPiece(layoutPiece) : VLayoutPiece(layoutPiece)
@ -239,3 +320,111 @@ void VPPiece::Flip()
SetMatrix(pieceMatrix); SetMatrix(pieceMatrix);
SetMirror(!IsMirror()); SetMirror(!IsMirror());
} }
//---------------------------------------------------------------------------------------------------------------------
auto VPPiece::StickyPosition(qreal &dx, qreal &dy) const -> bool
{
VPLayoutPtr layout = Layout();
if (layout.isNull() || not layout->LayoutSettings().GetStickyEdges())
{
return false;
}
const qreal pieceGap = layout->LayoutSettings().GetPiecesGap();
if (pieceGap <= 0)
{
return false;
}
VPSheetPtr sheet = Sheet();
if (sheet.isNull())
{
return false;
}
QList<VPPiecePtr> allPieces = sheet->GetPieces();
if (allPieces.count() < 2)
{
return false;
}
QVector<QPointF> path = GetMappedExternalContourPoints();
QRectF boundingRect = VLayoutPiece::BoundingRect(path);
const qreal stickyDistance = pieceGap+minStickyDistance;
QRectF stickyZone = QRectF(boundingRect.topLeft().x()-stickyDistance, boundingRect.topLeft().y()-stickyDistance,
boundingRect.width()+stickyDistance*2, boundingRect.height()+stickyDistance*2);
QVector<QPointF> stickyPath = PrepareStickyPath(path);
QLineF closestDistance;
for (const auto& piece : allPieces)
{
if (piece.isNull() || piece->GetUniqueID() == GetUniqueID())
{
continue;
}
QVector<QPointF> piecePath = piece->GetMappedExternalContourPoints();
QRectF pieceBoundingRect = VLayoutPiece::BoundingRect(piecePath);
if (stickyZone.intersects(pieceBoundingRect) || pieceBoundingRect.contains(stickyZone) ||
stickyZone.contains(pieceBoundingRect))
{
if (not VPPiece::PathsSuperposition(path, piecePath))
{
QVector<QPointF> pieceStickyPath = PrepareStickyPath(piecePath);
closestDistance = ClosestDistance(stickyPath, pieceStickyPath);
}
}
}
if (closestDistance.isNull())
{
return false;
}
const qreal extraZone = qBound(minStickyDistance, pieceGap * 50 / 100, maxStickyDistance);
const qreal length = closestDistance.length();
if (length > pieceGap && length <= pieceGap + extraZone)
{
closestDistance.setLength(length - pieceGap);
QPointF diff = closestDistance.p2() - closestDistance.p1();
dx = diff.x();
dy = diff.y();
return true;
}
if (length < pieceGap && length >= pieceGap - extraZone)
{
closestDistance.setAngle(closestDistance.angle() + 180);
closestDistance.setLength(pieceGap - length);
QPointF diff = closestDistance.p2() - closestDistance.p1();
dx = diff.x();
dy = diff.y();
return true;
}
return false;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPPiece::PathsSuperposition(const QVector<QPointF> &path1, const QVector<QPointF> &path2) -> bool
{
const QRectF path1Rect = VLayoutPiece::BoundingRect(path1);
const QPainterPath path1Path = VAbstractPiece::PainterPath(path1);
const QRectF path2Rect = VLayoutPiece::BoundingRect(path2);
const QPainterPath path2Path = VAbstractPiece::PainterPath(path2);
if (path1Rect.intersects(path2Rect) || path2Rect.contains(path1Rect) || path1Rect.contains(path2Rect))
{
if (path1Path.contains(path2Path) || path2Path.contains(path1Path) || path1Path.intersects(path2Path))
{
return true;
}
}
return false;
}

View File

@ -112,6 +112,10 @@ public:
auto HasSuperpositionWithPieces() const -> bool; auto HasSuperpositionWithPieces() const -> bool;
void SetHasSuperpositionWithPieces(bool newHasSuperpositionWithPieces); void SetHasSuperpositionWithPieces(bool newHasSuperpositionWithPieces);
auto StickyPosition(qreal &dx, qreal &dy) const -> bool;
static auto PathsSuperposition(const QVector<QPointF> &path1, const QVector<QPointF> &path2) -> bool;
private: private:
Q_DISABLE_COPY(VPPiece) Q_DISABLE_COPY(VPPiece)

View File

@ -184,7 +184,7 @@ void VPSheet::ValidateSuperpositionOfPieces() const
QVector<QPointF> path2 = p->GetMappedExternalContourPoints(); QVector<QPointF> path2 = p->GetMappedExternalContourPoints();
bool superposition = PathsSuperposition(path1, path2); bool superposition = VPPiece::PathsSuperposition(path1, path2);
if (superposition) if (superposition)
{ {
hasSuperposition = superposition; hasSuperposition = superposition;
@ -313,23 +313,3 @@ void VPSheet::CheckPiecePositionValidity(const VPPiecePtr &piece) const
ValidateSuperpositionOfPieces(); ValidateSuperpositionOfPieces();
} }
} }
//---------------------------------------------------------------------------------------------------------------------
auto VPSheet::PathsSuperposition(const QVector<QPointF> &path1, const QVector<QPointF> &path2) const -> bool
{
const QRectF path1Rect = VLayoutPiece::BoundingRect(path1);
const QPainterPath path1Path = VAbstractPiece::PainterPath(path1);
const QRectF path2Rect = VLayoutPiece::BoundingRect(path2);
const QPainterPath path2Path = VAbstractPiece::PainterPath(path2);
if (path1Rect.intersects(path2Rect) || path2Rect.contains(path1Rect) || path1Rect.contains(path2Rect))
{
if (path1Path.contains(path2Path) || path2Path.contains(path1Path) || path1Path.intersects(path2Path))
{
return true;
}
}
return false;
}

View File

@ -112,7 +112,7 @@ private:
VPTransformationOrigon m_transformationOrigin{}; VPTransformationOrigon m_transformationOrigin{};
auto PathsSuperposition(const QVector<QPointF> &path1, const QVector<QPointF> &path2) const -> bool;
}; };
Q_DECLARE_METATYPE(VPSheetPtr) Q_DECLARE_METATYPE(VPSheetPtr)

View File

@ -94,6 +94,7 @@ auto VPGraphicsPiece::boundingRect() const -> QRectF
shape.addPath(m_internalPaths); shape.addPath(m_internalPaths);
shape.addPath(m_passmarks); shape.addPath(m_passmarks);
shape.addPath(m_placeLabels); shape.addPath(m_placeLabels);
shape.addPath(m_stickyPath);
constexpr qreal halfPenWidth = penWidth/2.; constexpr qreal halfPenWidth = penWidth/2.;
@ -136,6 +137,7 @@ void VPGraphicsPiece::mousePressEvent(QGraphicsSceneMouseEvent *event)
m_moveStartPoint = event->pos(); m_moveStartPoint = event->pos();
emit HideTransformationHandles(true); emit HideTransformationHandles(true);
m_hasStickyPosition = false;
} }
} }
@ -161,7 +163,26 @@ void VPGraphicsPiece::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{ {
setCursor(Qt::OpenHandCursor); setCursor(Qt::OpenHandCursor);
emit HideTransformationHandles(false); emit HideTransformationHandles(false);
VPPiecePtr piece = m_piece.toStrongRef();
if (not piece.isNull())
{
VPLayoutPtr layout = piece->Layout();
if (not layout.isNull())
{
if (layout->LayoutSettings().GetStickyEdges() && m_hasStickyPosition)
{
auto *command = new VPUndoPieceMove(piece, m_stickyTranslateX, m_stickyTranslateY,
allowChangeMerge);
layout->UndoStack()->push(command);
SetStickyPoints(QVector<QPointF>());
}
}
}
allowChangeMerge = false; allowChangeMerge = false;
m_hasStickyPosition = false;
} }
} }
@ -218,6 +239,15 @@ void VPGraphicsPiece::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
} }
} }
//---------------------------------------------------------------------------------------------------------------------
void VPGraphicsPiece::SetStickyPoints(const QVector<QPointF> &newStickyPoint)
{
m_stickyPoints = newStickyPoint;
prepareGeometryChange();
PaintPiece(); // refresh shapes
}
//--------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------
void VPGraphicsPiece::PaintPieceLabel(const QVector<QPointF> &labelShape, const VTextManager &tm, QPainter *painter) void VPGraphicsPiece::PaintPieceLabel(const QVector<QPointF> &labelShape, const VTextManager &tm, QPainter *painter)
{ {
@ -329,6 +359,7 @@ void VPGraphicsPiece::PaintPiece(QPainter *painter)
m_internalPaths = QPainterPath(); m_internalPaths = QPainterPath();
m_passmarks = QPainterPath(); m_passmarks = QPainterPath();
m_placeLabels = QPainterPath(); m_placeLabels = QPainterPath();
m_stickyPath = QPainterPath();
VPPiecePtr piece = m_piece.toStrongRef(); VPPiecePtr piece = m_piece.toStrongRef();
if (piece.isNull()) if (piece.isNull())
@ -457,6 +488,29 @@ void VPGraphicsPiece::PaintPiece(QPainter *painter)
PaintPieceLabel(piece->GetPieceLabelRect(), piece->GetPieceLabelData(), painter); PaintPieceLabel(piece->GetPieceLabelRect(), piece->GetPieceLabelData(), painter);
PaintPieceLabel(piece->GetPatternLabelRect(), piece->GetPatternLabelData(), painter); PaintPieceLabel(piece->GetPatternLabelRect(), piece->GetPatternLabelData(), painter);
if (not m_stickyPoints.isEmpty())
{
m_stickyPath.moveTo(m_stickyPoints.first());
for (int i = 1; i < m_stickyPoints.size(); i++)
{
m_stickyPath.lineTo(m_stickyPoints.at(i));
}
if (painter != nullptr)
{
painter->save();
painter->setBrush(QBrush(Qt::BDiagPattern));
QPen pen = painter->pen();
pen.setStyle(Qt::DashLine);
pen.setColor(mainColor);
painter->setPen(pen);
painter->drawPath(m_stickyPath);
painter->restore();
}
}
} }
//--------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------
@ -497,8 +551,28 @@ void VPGraphicsPiece::GroupMove(const QPointF &pos)
if (pieces.size() == 1) if (pieces.size() == 1)
{ {
auto *command = new VPUndoPieceMove(pieces.first(), newPos.x(), newPos.y(), allowChangeMerge); VPPiecePtr p = pieces.first();
auto *command = new VPUndoPieceMove(piece, newPos.x(), newPos.y(), allowChangeMerge);
layout->UndoStack()->push(command); layout->UndoStack()->push(command);
if (layout->LayoutSettings().GetStickyEdges())
{
QVector<QPointF> path;
if (not p.isNull() && p->StickyPosition(m_stickyTranslateX, m_stickyTranslateY))
{
path = p->GetMappedExternalContourPoints();
QTransform m;
m.translate(m_stickyTranslateX, m_stickyTranslateY);
path = m.map(path);
m_hasStickyPosition = true;
}
else
{
m_hasStickyPosition = false;
}
SetStickyPoints(path);
}
} }
else if (pieces.size() > 1) else if (pieces.size() > 1)
{ {

View File

@ -53,6 +53,8 @@ public:
virtual int type() const override {return Type;} virtual int type() const override {return Type;}
enum { Type = UserType + static_cast<int>(PGraphicsItem::Piece)}; enum { Type = UserType + static_cast<int>(PGraphicsItem::Piece)};
void SetStickyPoints(const QVector<QPointF> &newStickyPoint);
signals: signals:
void HideTransformationHandles(bool hide); void HideTransformationHandles(bool hide);
void PieceTransformationChanged(); void PieceTransformationChanged();
@ -91,6 +93,13 @@ private:
bool allowChangeMerge{false}; bool allowChangeMerge{false};
QVector<QPointF> m_stickyPoints{};
QPainterPath m_stickyPath{};
bool m_hasStickyPosition{false};
qreal m_stickyTranslateX{0};
qreal m_stickyTranslateY{0};
void PaintPieceLabel(const QVector<QPointF> &labelShape, const VTextManager &tm, QPainter *painter=nullptr); void PaintPieceLabel(const QVector<QPointF> &labelShape, const VTextManager &tm, QPainter *painter=nullptr);
void PaintPiece(QPainter *painter=nullptr); void PaintPiece(QPainter *painter=nullptr);

View File

@ -302,7 +302,7 @@ void VPMainGraphicsView::keyPressEvent(QKeyEvent *event)
} }
else else
{ {
TranslatePiecesOn(0, 1); TranslatePiecesOn(0, -1);
} }
} }
else if (event->key() == Qt::Key_Down) else if (event->key() == Qt::Key_Down)
@ -360,7 +360,48 @@ void VPMainGraphicsView::keyReleaseEvent(QKeyEvent *event)
{ {
if (not event->isAutoRepeat()) if (not event->isAutoRepeat())
{ {
if (m_hasStickyPosition && not m_graphicsPieces.isEmpty())
{
VPPiecePtr piece = m_graphicsPieces.first()->GetPiece();
if (not piece.isNull())
{
VPLayoutPtr layout = piece->Layout();
if (not layout.isNull() && layout->LayoutSettings().GetStickyEdges())
{
auto PreparePieces = [layout]()
{
QList<VPPiecePtr> pieces;
VPSheetPtr sheet = layout->GetFocusedSheet();
if (not sheet.isNull())
{
pieces = sheet->GetSelectedPieces();
}
return pieces;
};
QList<VPPiecePtr> pieces = PreparePieces();
if (pieces.size() == 1)
{
VPPiecePtr p = pieces.first();
auto *command = new VPUndoPieceMove(p, m_stickyTranslateX, m_stickyTranslateY,
m_allowChangeMerge);
layout->UndoStack()->push(command);
VPGraphicsPiece * gPiece = ScenePiece(p);
if (gPiece != nullptr)
{
gPiece->SetStickyPoints(QVector<QPointF>());
}
}
}
}
}
m_allowChangeMerge = false; m_allowChangeMerge = false;
m_hasStickyPosition = false;
} }
} }
@ -632,8 +673,32 @@ void VPMainGraphicsView::TranslatePiecesOn(qreal dx, qreal dy)
QList<VPPiecePtr> pieces = PreparePieces(); QList<VPPiecePtr> pieces = PreparePieces();
if (pieces.size() == 1) if (pieces.size() == 1)
{ {
auto *command = new VPUndoPieceMove(pieces.first(), dx, dy, m_allowChangeMerge); VPPiecePtr p = pieces.first();
auto *command = new VPUndoPieceMove(p, dx, dy, m_allowChangeMerge);
layout->UndoStack()->push(command); layout->UndoStack()->push(command);
if (layout->LayoutSettings().GetStickyEdges())
{
QVector<QPointF> path;
if (not p.isNull() && p->StickyPosition(m_stickyTranslateX, m_stickyTranslateY))
{
path = p->GetMappedExternalContourPoints();
QTransform m;
m.translate(m_stickyTranslateX, m_stickyTranslateY);
path = m.map(path);
m_hasStickyPosition = true;
}
else
{
m_hasStickyPosition = false;
}
VPGraphicsPiece *gPiece = ScenePiece(p);
if (gPiece != nullptr)
{
gPiece->SetStickyPoints(path);
}
}
} }
else if (pieces.size() > 1) else if (pieces.size() > 1)
{ {
@ -645,7 +710,7 @@ void VPMainGraphicsView::TranslatePiecesOn(qreal dx, qreal dy)
} }
//--------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------
void VPMainGraphicsView::on_PieceSheetChanged(const VPPiecePtr &piece) auto VPMainGraphicsView::ScenePiece(const VPPiecePtr &piece) const -> VPGraphicsPiece *
{ {
VPGraphicsPiece *_graphicsPiece = nullptr; VPGraphicsPiece *_graphicsPiece = nullptr;
for(auto *graphicPiece : m_graphicsPieces) for(auto *graphicPiece : m_graphicsPieces)
@ -656,6 +721,14 @@ void VPMainGraphicsView::on_PieceSheetChanged(const VPPiecePtr &piece)
} }
} }
return _graphicsPiece;
}
//---------------------------------------------------------------------------------------------------------------------
void VPMainGraphicsView::on_PieceSheetChanged(const VPPiecePtr &piece)
{
VPGraphicsPiece *graphicsPiece = ScenePiece(piece);
VPLayoutPtr layout = piece->Layout(); VPLayoutPtr layout = piece->Layout();
if (layout.isNull()) if (layout.isNull())
{ {
@ -665,22 +738,22 @@ void VPMainGraphicsView::on_PieceSheetChanged(const VPPiecePtr &piece)
if (piece->Sheet().isNull() || piece->Sheet() == layout->GetTrashSheet() || if (piece->Sheet().isNull() || piece->Sheet() == layout->GetTrashSheet() ||
piece->Sheet() != layout->GetFocusedSheet()) // remove piece->Sheet() != layout->GetFocusedSheet()) // remove
{ {
if (_graphicsPiece != nullptr) if (graphicsPiece != nullptr)
{ {
scene()->removeItem(_graphicsPiece); scene()->removeItem(graphicsPiece);
m_graphicsPieces.removeAll(_graphicsPiece); m_graphicsPieces.removeAll(graphicsPiece);
delete _graphicsPiece; delete graphicsPiece;
} }
} }
else // add else // add
{ {
if(_graphicsPiece == nullptr) if(graphicsPiece == nullptr)
{ {
_graphicsPiece = new VPGraphicsPiece(piece); graphicsPiece = new VPGraphicsPiece(piece);
m_graphicsPieces.append(_graphicsPiece); m_graphicsPieces.append(graphicsPiece);
ConnectPiece(_graphicsPiece); ConnectPiece(graphicsPiece);
} }
scene()->addItem(_graphicsPiece); scene()->addItem(graphicsPiece);
} }
VMainGraphicsView::NewSceneRect(scene(), this); VMainGraphicsView::NewSceneRect(scene(), this);

View File

@ -126,11 +126,16 @@ private:
qreal m_rotationSum{0}; qreal m_rotationSum{0};
bool m_hasStickyPosition{false};
qreal m_stickyTranslateX{0};
qreal m_stickyTranslateY{0};
void ConnectPiece(VPGraphicsPiece *piece); void ConnectPiece(VPGraphicsPiece *piece);
void RotatePiecesByAngle(qreal angle); void RotatePiecesByAngle(qreal angle);
void TranslatePiecesOn(qreal dx, qreal dy); void TranslatePiecesOn(qreal dx, qreal dy);
auto ScenePiece(const VPPiecePtr &piece) const -> VPGraphicsPiece *;
}; };
#endif // VPMAINGRAPHICSVIEW_H #endif // VPMAINGRAPHICSVIEW_H

View File

@ -909,7 +909,6 @@ void VPMainWindow::InitPropertyTabLayout()
{ {
m_layout->LayoutSettings().SetPiecesGapConverted(d); m_layout->LayoutSettings().SetPiecesGapConverted(d);
LayoutWasSaved(false); LayoutWasSaved(false);
// TODO update the QGraphicView
} }
}); });
@ -2539,6 +2538,19 @@ void VPMainWindow::on_ApplyPieceTransformation()
auto *command = new VPUndoPieceMove(piece, pieceDx, pieceDy); auto *command = new VPUndoPieceMove(piece, pieceDx, pieceDy);
m_layout->UndoStack()->push(command); m_layout->UndoStack()->push(command);
if (m_layout->LayoutSettings().GetStickyEdges())
{
qreal stickyTranslateX = 0;
qreal stickyTranslateY = 0;
if (piece->StickyPosition(stickyTranslateX, stickyTranslateY))
{
bool allowMerge = selectedPieces.size() == 1;
auto *stickyCommand = new VPUndoPieceMove(piece, stickyTranslateX, stickyTranslateY,
allowMerge);
m_layout->UndoStack()->push(stickyCommand);
}
}
} }
} }

View File

@ -297,7 +297,7 @@ void VPLayoutFileReader::ReadControl(const VPLayoutPtr &layout)
ReadAttributeBool(attribs, ML::AttrWarningSuperposition, trueStr)); ReadAttributeBool(attribs, ML::AttrWarningSuperposition, trueStr));
layout->LayoutSettings().SetWarningPiecesOutOfBound(ReadAttributeBool(attribs, ML::AttrWarningOutOfBound, trueStr)); layout->LayoutSettings().SetWarningPiecesOutOfBound(ReadAttributeBool(attribs, ML::AttrWarningOutOfBound, trueStr));
layout->LayoutSettings().SetStickyEdges(ReadAttributeBool(attribs, ML::AttrStickyEdges, trueStr)); layout->LayoutSettings().SetStickyEdges(ReadAttributeBool(attribs, ML::AttrStickyEdges, trueStr));
layout->LayoutSettings().SetPiecesGap(ReadAttributeDouble(attribs, ML::AttrPiecesGap, QChar('0'))); layout->LayoutSettings().SetPiecesGap(qMax(ReadAttributeDouble(attribs, ML::AttrPiecesGap, QChar('0')), 0.0));
layout->LayoutSettings().SetFollowGrainline(ReadAttributeBool(attribs, ML::AttrFollowGrainline, falseStr)); layout->LayoutSettings().SetFollowGrainline(ReadAttributeBool(attribs, ML::AttrFollowGrainline, falseStr));
readElementText(); readElementText();