1 /************************************************************************
2  **
3  **  @file
4  **  @author Roman Telezhynskyi <dismine(at)gmail.com>
5  **  @date   3 11, 2016
6  **
7  **  @brief
8  **  @copyright
9  **  This source code is part of the Valentina project, a pattern making
10  **  program, whose allow create and modeling patterns of clothing.
11  **  Copyright (C) 2016 Valentina project
12  **  <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
13  **
14  **  Valentina is free software: you can redistribute it and/or modify
15  **  it under the terms of the GNU General Public License as published by
16  **  the Free Software Foundation, either version 3 of the License, or
17  **  (at your option) any later version.
18  **
19  **  Valentina is distributed in the hope that it will be useful,
20  **  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  **  GNU General Public License for more details.
23  **
24  **  You should have received a copy of the GNU General Public License
25  **  along with Valentina.  If not, see <http://www.gnu.org/licenses/>.
26  **
27  *************************************************************************/
28 
29 #include "vabstractpiece.h"
30 #include "vabstractpiece_p.h"
31 #include "../vmisc/vabstractvalapplication.h"
32 #include "../vgeometry/vpointf.h"
33 #include "../ifc/exception/vexception.h"
34 #include "../vmisc/vmath.h"
35 #include "../vmisc/compatibility.h"
36 #include "../vpatterndb/floatItemData/vgrainlinedata.h"
37 #include "../vpatterndb/vcontainer.h"
38 #include "../vpatterndb/calculator.h"
39 #include "testpath.h"
40 #include "vrawsapoint.h"
41 
42 #include <QLineF>
43 #include <QSet>
44 #include <QVector>
45 #include <QPainterPath>
46 #include <QTemporaryFile>
47 #include <QJsonObject>
48 #include <QJsonArray>
49 #include <QJsonDocument>
50 
51 const quint32 VAbstractPieceData::streamHeader = 0x05CDD73A; // CRC-32Q string "VAbstractPieceData"
52 const quint16 VAbstractPieceData::classVersion = 2;
53 
54 const qreal maxL = 3.5;
55 
56 const qreal VSAPoint::passmarkFactor = 0.5;
57 const qreal VSAPoint::maxPassmarkLength = (10/*mm*/ / 25.4) * PrintDPI;
58 const qreal VSAPoint::minSAWidth = ToPixel(0.015, Unit::Cm);
59 
60 namespace
61 {
62 //---------------------------------------------------------------------------------------------------------------------
IsSameDirection(QPointF p1,QPointF p2,QPointF px)63 inline bool IsSameDirection(QPointF p1, QPointF p2, QPointF px)
64 {
65     return qAbs(QLineF(p1, p2).angle() - QLineF(p1, px).angle()) < 0.001;
66 }
67 
68 //---------------------------------------------------------------------------------------------------------------------
69 // Do we create a point outside of a path?
IsOutsidePoint(QPointF p1,QPointF p2,QPointF px)70 inline bool IsOutsidePoint(QPointF p1, QPointF p2, QPointF px)
71 {
72     QLineF seg1(p1, p2);
73     QLineF seg2(p1, px);
74 
75     return IsSameDirection(p1, p2, px) && seg2.length() >= seg1.length();
76 }
77 
78 //---------------------------------------------------------------------------------------------------------------------
PointPosition(const QPointF & p,const QLineF & line)79 Q_DECL_CONSTEXPR qreal PointPosition(const QPointF &p, const QLineF &line)
80 {
81     return (line.p2().x() - line.p1().x()) * (p.y() - line.p1().y()) -
82            (line.p2().y() - line.p1().y()) * (p.x() - line.p1().x());
83 }
84 
85 //---------------------------------------------------------------------------------------------------------------------
AngleByLength(QVector<VRawSAPoint> points,QPointF p1,QPointF p2,QPointF p3,const QLineF & bigLine1,QPointF sp2,const QLineF & bigLine2,const VSAPoint & p,qreal width,bool * needRollback=nullptr)86 QVector<VRawSAPoint> AngleByLength(QVector<VRawSAPoint> points, QPointF p1, QPointF p2, QPointF p3,
87                                    const QLineF &bigLine1, QPointF sp2, const QLineF &bigLine2, const VSAPoint &p,
88                                    qreal width, bool *needRollback = nullptr)
89 {
90     if (needRollback != nullptr)
91     {
92         *needRollback = false;
93     }
94 
95     const QPointF sp1 = bigLine1.p1();
96     const QPointF sp3 = bigLine2.p2();
97     const qreal localWidth = p.MaxLocalSA(width);
98 
99     if (IsOutsidePoint(bigLine1.p1(), bigLine1.p2(), sp2) && IsOutsidePoint(bigLine2.p2(), bigLine2.p1(), sp2) )
100     {
101         QLineF line(p2, sp2);
102         const qreal length = line.length();
103         if (length > localWidth*maxL)
104         { // Cutting too long acut angle
105             line.setLength(localWidth);
106             QLineF cutLine(line.p2(), sp2); // Cut line is a perpendicular
107             cutLine.setLength(length); // Decided to take this length
108 
109             // We do not check intersection type because intersection must alwayse exist
110             QPointF px;
111             cutLine.setAngle(cutLine.angle()+90);
112             QLineF::IntersectType type = Intersects(QLineF(sp1, sp2), cutLine, &px);
113 
114             if (type == QLineF::NoIntersection)
115             {
116                 qDebug()<<"Couldn't find intersection with cut line.";
117             }
118             points.append(px);
119 
120             cutLine.setAngle(cutLine.angle()-180);
121             type = Intersects(QLineF(sp2, sp3), cutLine, &px);
122 
123             if (type == QLineF::NoIntersection)
124             {
125                 qDebug()<<"Couldn't find intersection with cut line.";
126             }
127             points.append(px);
128         }
129         else
130         {// The point just fine
131             points.append(sp2);
132         }
133     }
134     else
135     {
136         QLineF edge1(p2, p1);
137         QLineF edge2(p2, p3);
138         const qreal angle = edge1.angleTo(edge2);
139 
140         if (angle > 180 && p.GetAngleType() != PieceNodeAngle::ByLengthCurve)
141         {
142             if (VGObject::IsPointOnLineSegment(sp2, bigLine2.p1(), bigLine2.p2()))
143             {
144                 QLineF loop(bigLine1.p2(), sp2);
145                 loop.setLength(loop.length() + accuracyPointOnLine*2.);
146                 points.append(loop.p2());
147                 points.append(sp2);
148                 points.append(VRawSAPoint(bigLine1.p2(), true));
149 
150                 loop = QLineF(bigLine2.p2(), sp2);
151                 loop.setLength(loop.length() + localWidth);
152                 points.append(VRawSAPoint(loop.p2(), true));
153             }
154             else
155             {
156                 QLineF loop(sp2, bigLine1.p1());
157                 loop.setLength(accuracyPointOnLine*2.);
158                 points.append(loop.p2());
159                 points.append(sp2);
160 
161                 loop = QLineF(bigLine1.p1(), sp2);
162                 loop.setLength(loop.length() + localWidth);
163                 points.append(VRawSAPoint(loop.p2(), true));
164                 points.append(VRawSAPoint(bigLine2.p1(), true));
165             }
166         }
167         else
168         {
169             if (not IsOutsidePoint(bigLine1.p1(), bigLine1.p2(), sp2))
170             {
171                 if (p.GetAngleType() != PieceNodeAngle::ByLengthCurve)
172                 {
173                     bool success = false;
174                     QVector<VRawSAPoint> temp = points;
175                     temp.append(bigLine1.p2());
176                     temp = VAbstractPiece::RollbackSeamAllowance(temp, bigLine2, &success);
177 
178                     if (success)
179                     {
180                         points = temp;
181                     }
182 
183                     if (needRollback != nullptr)
184                     {
185                         *needRollback = not success;
186                     }
187                 }
188                 else
189                 {
190                     points.append(sp2);
191                 }
192             }
193             else
194             {
195                 if (p.GetAngleType() != PieceNodeAngle::ByLengthCurve)
196                 {
197                     // Need to create artificial loop
198                     QLineF loop1(sp2, sp1);
199                     loop1.setLength(loop1.length()*0.2);
200 
201                     points.append(loop1.p2()); // Need for the main path rule
202 
203                     loop1.setAngle(loop1.angle() + 180);
204                     loop1.setLength(localWidth);
205                     points.append(loop1.p2());
206                     points.append(bigLine2.p1());
207                 }
208                 else
209                 {
210                     points.append(sp2);
211                 }
212             }
213         }
214     }
215     return points;
216 }
217 
218 //---------------------------------------------------------------------------------------------------------------------
AngleByIntersection(const QVector<VRawSAPoint> & points,QPointF p1,QPointF p2,QPointF p3,const QLineF & bigLine1,QPointF sp2,const QLineF & bigLine2,const VSAPoint & p,qreal width,bool * needRollback=nullptr)219 QVector<VRawSAPoint> AngleByIntersection(const QVector<VRawSAPoint> &points, QPointF p1, QPointF p2, QPointF p3,
220                                          const QLineF &bigLine1, QPointF sp2, const QLineF &bigLine2,
221                                          const VSAPoint &p, qreal width, bool *needRollback = nullptr)
222 {
223     {
224         QLineF edge1(p2, p1);
225         QLineF edge2(p2, p3);
226         const qreal angle = edge1.angleTo(edge2);
227 
228         if (angle > 180)
229         {
230             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
231         }
232     }
233 
234     if (needRollback != nullptr)
235     {
236         *needRollback = false;
237     }
238 
239     const qreal localWidth = p.MaxLocalSA(width);
240     QVector<VRawSAPoint> pointsIntr = points;
241 
242     // First point
243     QLineF edge2(p2, p3);
244 
245     QPointF px;
246     QLineF::IntersectType type = Intersects(edge2, bigLine1, &px);
247 
248     if (type == QLineF::NoIntersection)
249     {
250         return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
251     }
252 
253     if (IsOutsidePoint(bigLine1.p1(), bigLine1.p2(), px))
254     {
255         if (QLineF(p2, px).length() > localWidth*maxL)
256         {
257             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
258         }
259         pointsIntr.append(px);
260     }
261     else
262     {// Because artificial loop can lead to wrong clipping we must rollback current seam allowance points
263         bool success = false;
264         QVector<VRawSAPoint> temp = pointsIntr;
265         temp.append(bigLine1.p2());
266         temp = VAbstractPiece::RollbackSeamAllowance(temp, edge2, &success);
267 
268         if (success)
269         {
270             pointsIntr = temp;
271         }
272 
273         if (needRollback != nullptr)
274         {
275             *needRollback = not success;
276         }
277     }
278 
279     // Second point
280     QLineF edge1(p1, p2);
281     type = Intersects(edge1, bigLine2, &px);
282 
283     if (type == QLineF::NoIntersection)
284     {
285         return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
286     }
287 
288     if (IsOutsidePoint(bigLine2.p2(), bigLine2.p1(), px))
289     {
290         pointsIntr.append(px);
291     }
292     else
293     {
294         pointsIntr.append(px);
295 
296         QLineF allowance(p2, px);
297         allowance.setLength(allowance.length() + localWidth * 3.);
298         pointsIntr.append(allowance.p2());
299         pointsIntr.append(bigLine2.p1());
300     }
301 
302     return pointsIntr;
303 }
304 
305 //---------------------------------------------------------------------------------------------------------------------
AngleByFirstSymmetry(const QVector<VRawSAPoint> & points,QPointF p1,QPointF p2,QPointF p3,const QLineF & bigLine1,QPointF sp2,const QLineF & bigLine2,const VSAPoint & p,qreal width,bool * needRollback=nullptr)306 QVector<VRawSAPoint> AngleByFirstSymmetry(const QVector<VRawSAPoint> &points, QPointF p1, QPointF p2, QPointF p3,
307                                           const QLineF &bigLine1, QPointF sp2, const QLineF &bigLine2,
308                                           const VSAPoint &p, qreal width, bool *needRollback = nullptr)
309 {
310     {
311         QLineF edge1(p2, p1);
312         QLineF edge2(p2, p3);
313         const qreal angle = edge1.angleTo(edge2);
314 
315         if (angle > 180)
316         {
317             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
318         }
319     }
320 
321     if (needRollback != nullptr)
322     {
323         *needRollback = false;
324     }
325 
326     const QLineF axis = QLineF(p1, p2);
327 
328     QLineF sEdge(VPointF::FlipPF(axis, bigLine2.p1()), VPointF::FlipPF(axis, bigLine2.p2()));
329 
330     QPointF px1;
331     QLineF::IntersectType type = Intersects(sEdge, bigLine1, &px1);
332 
333     if (type == QLineF::NoIntersection)
334     {
335         return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
336     }
337 
338     QPointF px2;
339     type = Intersects(sEdge, bigLine2, &px2);
340 
341     if (type == QLineF::NoIntersection)
342     {
343         return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
344     }
345 
346     QVector<VRawSAPoint> pointsIntr = points;
347 
348     if (IsOutsidePoint(bigLine1.p1(), bigLine1.p2(), px1))
349     {
350         pointsIntr.append(px1);
351     }
352     else
353     {// Because artificial loop can lead to wrong clipping we must rollback current seam allowance points
354         bool success = false;
355         QVector<VRawSAPoint> temp = pointsIntr;
356         temp.append(bigLine1.p2());
357         temp = VAbstractPiece::RollbackSeamAllowance(temp, sEdge, &success);
358 
359         if (success)
360         {
361             pointsIntr = temp;
362         }
363 
364         if (needRollback != nullptr)
365         {
366             *needRollback = not success;
367         }
368     }
369 
370     if (IsOutsidePoint(bigLine2.p2(), bigLine2.p1(), px2))
371     {
372         pointsIntr.append(px2);
373     }
374     else
375     {
376         QLineF allowance(px2, p2);
377         allowance.setAngle(allowance.angle() + 90);
378         pointsIntr.append(px2);
379         pointsIntr.append(allowance.p2());
380         pointsIntr.append(bigLine2.p1());
381     }
382 
383     return pointsIntr;
384 }
385 
386 //---------------------------------------------------------------------------------------------------------------------
AngleBySecondSymmetry(const QVector<VRawSAPoint> & points,QPointF p1,QPointF p2,QPointF p3,const QLineF & bigLine1,QPointF sp2,const QLineF & bigLine2,const VSAPoint & p,qreal width,bool * needRollback=nullptr)387 QVector<VRawSAPoint> AngleBySecondSymmetry(const QVector<VRawSAPoint> &points, QPointF p1, QPointF p2, QPointF p3,
388                                            const QLineF &bigLine1, QPointF sp2, const QLineF &bigLine2,
389                                            const VSAPoint &p, qreal width, bool *needRollback = nullptr)
390 {
391     {
392         QLineF edge1(p2, p1);
393         QLineF edge2(p2, p3);
394         const qreal angle = edge1.angleTo(edge2);
395 
396         if (angle > 180)
397         {
398             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
399         }
400     }
401 
402     if (needRollback != nullptr)
403     {
404         *needRollback = false;
405     }
406 
407     const QLineF axis = QLineF(p3, p2);
408 
409     QLineF sEdge(VPointF::FlipPF(axis, bigLine1.p1()), VPointF::FlipPF(axis, bigLine1.p2()));
410 
411     QPointF px1;
412     QLineF::IntersectType type = Intersects(sEdge, bigLine1, &px1);
413 
414     if (type == QLineF::NoIntersection)
415     {
416         return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
417     }
418 
419     QPointF px2;
420     type = Intersects(sEdge, bigLine2, &px2);
421 
422     if (type == QLineF::NoIntersection)
423     {
424         return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
425     }
426 
427     const qreal localWidth = p.MaxLocalSA(width);
428     QVector<VRawSAPoint> pointsIntr = points;
429 
430     if (IsOutsidePoint(bigLine1.p1(), bigLine1.p2(), px1))
431     {
432         pointsIntr.append(px1);
433     }
434     else
435     {// Because artificial loop can lead to wrong clipping we must rollback current seam allowance points
436         bool success = false;
437         QVector<VRawSAPoint> temp = pointsIntr;
438         temp.append(bigLine1.p2());
439         temp = VAbstractPiece::RollbackSeamAllowance(temp, sEdge, &success);
440 
441         if (success)
442         {
443             pointsIntr = temp;
444         }
445 
446         if (needRollback != nullptr)
447         {
448             *needRollback = not success;
449         }
450     }
451 
452     if (IsOutsidePoint(bigLine2.p2(), bigLine2.p1(), px2))
453     {
454         pointsIntr.append(px2);
455     }
456     else
457     {
458         QLineF allowance(p2, px2);
459         allowance.setLength(p.GetSAAfter(width)*0.98);
460         pointsIntr.append(allowance.p2());
461         allowance.setLength(allowance.length() + localWidth * 3.);
462         pointsIntr.append(allowance.p2());
463         pointsIntr.append(bigLine2.p1());
464     }
465 
466     return pointsIntr;
467 }
468 
469 //---------------------------------------------------------------------------------------------------------------------
AngleByFirstRightAngle(const QVector<VRawSAPoint> & points,QPointF p1,QPointF p2,QPointF p3,const QLineF & bigLine1,QPointF sp2,const QLineF & bigLine2,const VSAPoint & p,qreal width,bool * needRollback=nullptr)470 QVector<VRawSAPoint> AngleByFirstRightAngle(const QVector<VRawSAPoint> &points, QPointF p1, QPointF p2, QPointF p3,
471                                             const QLineF &bigLine1, QPointF sp2, const QLineF &bigLine2,
472                                             const VSAPoint &p, qreal width, bool *needRollback = nullptr)
473 {
474     {
475         QLineF edge1(p2, p1);
476         QLineF edge2(p2, p3);
477         const qreal angle = edge1.angleTo(edge2);
478 
479         if (angle > 270)
480         {
481             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
482         }
483     }
484 
485     const qreal localWidth = p.MaxLocalSA(width);
486     QVector<VRawSAPoint> pointsRA = points;
487     QLineF edge(p1, p2);
488 
489     QPointF px;
490     QLineF::IntersectType type = Intersects(edge, bigLine2, &px);
491 
492     if (type == QLineF::NoIntersection)
493     {
494         return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
495     }
496 
497     QLineF seam(px, p1);
498     seam.setAngle(seam.angle()-90);
499     seam.setLength(p.GetSABefore(width));
500 
501     if (IsOutsidePoint(bigLine2.p2(), bigLine2.p1(), seam.p1()) && IsSameDirection(p1, p2, px))
502     {
503         if (QLineF(p2, px).length() > localWidth*maxL)
504         {
505             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
506         }
507         pointsRA.append(seam.p2());
508         pointsRA.append(seam.p1());
509     }
510     else
511     {
512         QLineF edge1(p2, p1);
513         QLineF edge2(p2, p3);
514         const qreal angle = edge1.angleTo(edge2);
515 
516         if (angle > 180)
517         {
518             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
519         }
520         else
521         {
522             pointsRA.append(seam.p2());
523 
524             QLineF loopLine(px, sp2);
525             const qreal length = loopLine.length()*0.98;
526             loopLine.setLength(length);
527 
528             QLineF tmp(seam.p2(), seam.p1());
529             tmp.setLength(tmp.length()+length);
530 
531             pointsRA.append(tmp.p2());
532             pointsRA.append(loopLine.p2());
533         }
534     }
535 
536     return pointsRA;
537 }
538 
539 //---------------------------------------------------------------------------------------------------------------------
AngleBySecondRightAngle(QVector<VRawSAPoint> points,QPointF p1,QPointF p2,QPointF p3,const QLineF & bigLine1,QPointF sp2,const QLineF & bigLine2,const VSAPoint & p,qreal width,bool * needRollback=nullptr)540 QVector<VRawSAPoint> AngleBySecondRightAngle(QVector<VRawSAPoint> points, QPointF p1, QPointF p2, QPointF p3,
541                                              const QLineF &bigLine1,  QPointF sp2, const QLineF &bigLine2,
542                                              const VSAPoint &p, qreal width, bool *needRollback = nullptr)
543 {
544     {
545         QLineF edge1(p2, p1);
546         QLineF edge2(p2, p3);
547         const qreal angle = edge1.angleTo(edge2);
548 
549         if (angle > 270)
550         {
551             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
552         }
553     }
554 
555     if (needRollback != nullptr)
556     {
557         *needRollback = false;
558     }
559 
560     const qreal localWidth = p.MaxLocalSA(width);
561     QLineF edge(p2, p3);
562 
563     QPointF px;
564     QLineF::IntersectType type = Intersects(edge, bigLine1, &px);
565 
566     if (type == QLineF::NoIntersection)
567     {
568         return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
569     }
570 
571     if (IsOutsidePoint(bigLine1.p1(), bigLine1.p2(), px) && IsSameDirection(p3, p2, px))
572     {
573         if (QLineF(p2, px).length() > localWidth*maxL)
574         {
575             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
576         }
577         points.append(px);
578 
579         QLineF seam(px, p3);
580         seam.setAngle(seam.angle()+90);
581         seam.setLength(p.GetSAAfter(width));
582         points.append(seam.p2());
583 
584         if (needRollback != nullptr)
585         {
586             *needRollback = true;
587         }
588     }
589     else
590     {
591         QLineF edge1(p2, p1);
592         QLineF edge2(p2, p3);
593         const qreal angle = edge1.angleTo(edge2);
594 
595         if (angle > 180)
596         {
597             return AngleByLength(points, p1, p2, p3, bigLine1, sp2, bigLine2, p, width, needRollback);
598         }
599 
600         // Because artificial loop can lead to wrong clipping we must rollback current seam allowance points
601         bool success = false;
602         const int countBefore = points.size();
603         QVector<VRawSAPoint> temp = points;
604         temp.append(bigLine1.p2());
605         temp = VAbstractPiece::RollbackSeamAllowance(temp, edge, &success);
606 
607         if (success)
608         {
609             points = temp;
610             px = points.last();
611         }
612 
613         if (countBefore > 0)
614         {
615             QLineF seam(px, p3);
616             seam.setAngle(seam.angle()+90);
617             seam.setLength(p.GetSAAfter(width));
618             points.append(seam.p2());
619         }
620         else
621         {
622             if (needRollback != nullptr)
623             {
624                 *needRollback = not success;
625             }
626             else if (IsSameDirection(bigLine1.p1(), bigLine1.p2(), px))
627             {
628                 points.append(px);
629                 QLineF seam(px, p3);
630                 seam.setAngle(seam.angle()+90);
631                 seam.setLength(p.GetSAAfter(width));
632                 points.append(seam.p2());
633             }
634         }
635     }
636 
637     return points;
638 }
639 
640 //---------------------------------------------------------------------------------------------------------------------
SingleParallelPoint(const QPointF & p1,const QPointF & p2,qreal angle,qreal width)641 QPointF SingleParallelPoint(const QPointF &p1, const QPointF &p2, qreal angle, qreal width)
642 {
643     QLineF pLine(p1, p2);
644     pLine.setAngle( pLine.angle() + angle );
645     pLine.setLength( width );
646     return pLine.p2();
647 }
648 
649 //---------------------------------------------------------------------------------------------------------------------
SimpleParallelLine(const QPointF & p1,const QPointF & p2,qreal width)650 QLineF SimpleParallelLine(const QPointF &p1, const QPointF &p2, qreal width)
651 {
652     const QLineF paralel = QLineF(SingleParallelPoint(p1, p2, 90, width),
653                                   SingleParallelPoint(p2, p1, -90, width));
654     return paralel;
655 }
656 
657 //---------------------------------------------------------------------------------------------------------------------
BisectorLine(const QPointF & p1,const QPointF & p2,const QPointF & p3)658 QLineF BisectorLine(const QPointF &p1, const QPointF &p2, const QPointF &p3)
659 {
660     QLineF line1(p2, p1);
661     QLineF line2(p2, p3);
662     QLineF bLine;
663 
664     const qreal angle1 = line1.angleTo(line2);
665     const qreal angle2 = line2.angleTo(line1);
666 
667     if (angle1 <= angle2)
668     {
669         bLine = line1;
670         bLine.setAngle(bLine.angle() + angle1/2.0);
671     }
672     else
673     {
674         bLine = line2;
675         bLine.setAngle(bLine.angle() + angle2/2.0);
676     }
677 
678     return bLine;
679 }
680 
681 //---------------------------------------------------------------------------------------------------------------------
AngleBetweenBisectors(const QLineF & b1,const QLineF & b2)682 qreal AngleBetweenBisectors(const QLineF &b1, const QLineF &b2)
683 {
684     const QLineF newB2 = b2.translated(-(b2.p1().x() - b1.p1().x()), -(b2.p1().y() - b1.p1().y()));
685 
686     qreal angle1 = newB2.angleTo(b1);
687     if (VFuzzyComparePossibleNulls(angle1, 360))
688     {
689         angle1 = 0;
690     }
691 
692     qreal angle2 = b1.angleTo(newB2);
693     if (VFuzzyComparePossibleNulls(angle2, 360))
694     {
695         angle2 = 0;
696     }
697 
698     return qMin(angle1, angle2);
699 }
700 
701 //---------------------------------------------------------------------------------------------------------------------
702 template<class T>
CorrectPathDistortion(QVector<T> path)703 QVector<T> CorrectPathDistortion(QVector<T> path)
704 {
705     if (path.size() < 3)
706     {
707         return path;
708     }
709 
710     int prev = -1;
711     for (qint32 i = 0; i < path.size(); ++i)
712     {
713         if (prev == -1)
714         {
715             i == 0 ? prev = path.size() - 1 : prev = i-1;
716         }
717 
718         int next = i+1;
719         if (i == path.size() - 1)
720         {
721             next = 0;
722         }
723 
724         const QPointF &iPoint = path.at(i);
725         const QPointF &prevPoint = path.at(prev);
726         const QPointF &nextPoint = path.at(next);
727 
728         if (VGObject::IsPointOnLineSegment(iPoint, prevPoint, nextPoint))
729         {
730             const QPointF p = VGObject::CorrectDistortion(iPoint, prevPoint, nextPoint);
731             path[i].setX(p.x());
732             path[i].setY(p.y());
733         }
734     }
735 
736     return path;
737 }
738 
739 //---------------------------------------------------------------------------------------------------------------------
Rollback(QVector<VRawSAPoint> & points,const QLineF & edge)740 bool Rollback(QVector<VRawSAPoint> &points, const QLineF &edge)
741 {
742     bool success = false;
743     if (not points.isEmpty())
744     {
745         points.removeLast();
746         points = VAbstractPiece::RollbackSeamAllowance(points, edge, &success);
747 
748         if (not points.isEmpty())
749         {
750             if (points.last().toPoint() != points.first().toPoint())
751             {
752                 points.append(points.first());// Should be always closed
753             }
754         }
755     }
756     return success;
757 }
758 
759 //---------------------------------------------------------------------------------------------------------------------
RollbackByLength(QVector<VRawSAPoint> & ekvPoints,const QVector<VSAPoint> & points,qreal width)760 void RollbackByLength(QVector<VRawSAPoint> &ekvPoints, const QVector<VSAPoint> &points, qreal width)
761 {
762     const QLineF bigLine1 = VAbstractPiece::ParallelLine(points.at(points.size()-2), points.at(0), width);
763 
764     QVector<VRawSAPoint> temp = ekvPoints;
765     temp.insert(ekvPoints.size()-1, bigLine1.p2());
766     bool success = Rollback(temp, VAbstractPiece::ParallelLine(points.at(0), points.at(1), width));
767 
768     if (success)
769     {
770         ekvPoints = temp;
771     }
772 }
773 
774 //---------------------------------------------------------------------------------------------------------------------
RollbackBySecondEdgeSymmetry(QVector<VRawSAPoint> & ekvPoints,const QVector<VSAPoint> & points,qreal width)775 void RollbackBySecondEdgeSymmetry(QVector<VRawSAPoint> &ekvPoints, const QVector<VSAPoint> &points, qreal width)
776 {
777     const QLineF axis = QLineF(points.at(points.size()-1), points.at(1));
778     const QLineF bigLine1 = VAbstractPiece::ParallelLine(points.at(points.size()-2), points.at(0), width);
779     QLineF sEdge(VPointF::FlipPF(axis, bigLine1.p1()), VPointF::FlipPF(axis, bigLine1.p2()));
780 
781     QVector<VRawSAPoint> temp = ekvPoints;
782     temp.insert(ekvPoints.size()-1, bigLine1.p2());
783     bool success = Rollback(temp, sEdge);
784 
785     if (success)
786     {
787         ekvPoints = temp;
788     }
789 }
790 
791 //---------------------------------------------------------------------------------------------------------------------
RollbackByFirstEdgeSymmetry(QVector<VRawSAPoint> & ekvPoints,const QVector<VSAPoint> & points,qreal width)792 void RollbackByFirstEdgeSymmetry(QVector<VRawSAPoint> &ekvPoints, const QVector<VSAPoint> &points, qreal width)
793 {
794     const QLineF axis = QLineF(points.at(points.size()-2), points.at(points.size()-1));
795     const QLineF bigLine2 = VAbstractPiece::ParallelLine(points.at(points.size()-1), points.at(1), width);
796     QLineF sEdge(VPointF::FlipPF(axis, bigLine2.p1()), VPointF::FlipPF(axis, bigLine2.p2()));
797     const QLineF bigLine1 = VAbstractPiece::ParallelLine(points.at(points.size()-2), points.at(0), width);
798 
799     QVector<VRawSAPoint> temp = ekvPoints;
800     temp.insert(ekvPoints.size()-1, bigLine1.p2());
801     bool success = Rollback(temp, sEdge);
802 
803     if (success)
804     {
805         ekvPoints = temp;
806     }
807 }
808 
809 //---------------------------------------------------------------------------------------------------------------------
RollbackByPointsIntersection(QVector<VRawSAPoint> & ekvPoints,const QVector<VSAPoint> & points,qreal width)810 void RollbackByPointsIntersection(QVector<VRawSAPoint> &ekvPoints, const QVector<VSAPoint> &points, qreal width)
811 {
812     const QLineF bigLine1 = VAbstractPiece::ParallelLine(points.at(points.size()-2), points.at(0), width);
813     QVector<VRawSAPoint> temp = ekvPoints;
814     temp.insert(ekvPoints.size()-1, bigLine1.p2());
815     bool success = Rollback(temp, QLineF(points.last(), points.at(1)));
816 
817     if (success)
818     {
819         ekvPoints = temp;
820     }
821 
822     if (ekvPoints.size() > 2)
823     { // Fix for the rule of main path
824         ekvPoints.removeAt(ekvPoints.size()-1);
825         ekvPoints.prepend(ekvPoints.at(ekvPoints.size()-1));
826     }
827 }
828 
829 //---------------------------------------------------------------------------------------------------------------------
RollbackBySecondEdgeRightAngle(QVector<VRawSAPoint> & ekvPoints,const QVector<VSAPoint> & points,qreal width)830 void RollbackBySecondEdgeRightAngle(QVector<VRawSAPoint> &ekvPoints, const QVector<VSAPoint> &points, qreal width)
831 {
832     if (not ekvPoints.isEmpty())
833     {
834         const QLineF edge(points.last(), points.at(1));
835         const QLineF bigLine1 = VAbstractPiece::ParallelLine(points.at(points.size()-2), points.at(0), width);
836 
837         QPointF px;
838         Intersects(edge, bigLine1, &px);
839 
840         ekvPoints.removeLast();
841 
842         if (IsOutsidePoint(bigLine1.p1(), bigLine1.p2(), px))
843         {
844             if (ekvPoints.size() > 3)
845             {
846                 const QLineF edge1(ekvPoints.at(ekvPoints.size()-2), ekvPoints.last());
847                 const QLineF edge2(ekvPoints.at(0), ekvPoints.at(1));
848 
849                 QPointF crosPoint;
850                 const QLineF::IntersectType type = Intersects(edge1, edge2, &crosPoint );
851 
852                 if (type == QLineF::BoundedIntersection)
853                 {
854                     ekvPoints.removeFirst();
855                     ekvPoints.removeLast();
856 
857                     ekvPoints.append(crosPoint);
858                 }
859             }
860         }
861         else
862         {
863             bool success = false;
864             QVector<VRawSAPoint> temp = ekvPoints;
865             temp.append(bigLine1.p2());
866             temp = VAbstractPiece::RollbackSeamAllowance(temp, edge, &success);
867 
868             if (success)
869             {
870                 ekvPoints = temp;
871                 px = ekvPoints.last();
872             }
873 
874             QLineF seam(px, points.at(1));
875             seam.setAngle(seam.angle()+90);
876             seam.setLength(points.at(0).GetSAAfter(width));
877             ekvPoints.append(seam.p2());
878 
879             if (not ekvPoints.isEmpty())
880             {
881                 ekvPoints.append(ekvPoints.first());
882             }
883         }
884 
885         if (not ekvPoints.isEmpty())
886         {
887             if (ekvPoints.last().toPoint() != ekvPoints.first().toPoint())
888             {
889                 ekvPoints.append(ekvPoints.first());// Should be always closed
890             }
891         }
892     }
893 }
894 
895 //---------------------------------------------------------------------------------------------------------------------
CleanLoopArtifacts(const QVector<VRawSAPoint> & points)896 QVector<QPointF> CleanLoopArtifacts(const QVector<VRawSAPoint> &points)
897 {
898     QVector<QPointF> cleaned;
899     cleaned.reserve(points.size());
900     for (const auto &point : points)
901     {
902         if (not point.LoopPoint())
903         {
904             cleaned.append(point);
905         }
906     }
907 
908     return cleaned;
909 }
910 }
911 
912 // Friend functions
913 //---------------------------------------------------------------------------------------------------------------------
operator <<(QDataStream & dataStream,const VAbstractPiece & piece)914 QDataStream &operator<<(QDataStream &dataStream, const VAbstractPiece &piece)
915 {
916     dataStream << *piece.d;
917     return dataStream;
918 }
919 
920 //---------------------------------------------------------------------------------------------------------------------
operator >>(QDataStream & dataStream,VAbstractPiece & piece)921 QDataStream &operator>>(QDataStream &dataStream, VAbstractPiece &piece)
922 {
923     dataStream >> *piece.d;
924     return dataStream;
925 }
926 
927 //---------------------------------------------------------------------------------------------------------------------
VAbstractPiece()928 VAbstractPiece::VAbstractPiece()
929     : d(new VAbstractPieceData)
930 {}
931 
932 //---------------------------------------------------------------------------------------------------------------------
VAbstractPiece(const VAbstractPiece & piece)933 VAbstractPiece::VAbstractPiece(const VAbstractPiece &piece)
934     :d (piece.d)
935 {}
936 
937 //---------------------------------------------------------------------------------------------------------------------
operator =(const VAbstractPiece & piece)938 VAbstractPiece &VAbstractPiece::operator=(const VAbstractPiece &piece)
939 {
940     if ( &piece == this )
941     {
942         return *this;
943     }
944     d = piece.d;
945     return *this;
946 }
947 
948 #ifdef Q_COMPILER_RVALUE_REFS
949 //---------------------------------------------------------------------------------------------------------------------
VAbstractPiece(const VAbstractPiece && piece)950 VAbstractPiece::VAbstractPiece(const VAbstractPiece &&piece) Q_DECL_NOTHROW
951     :d (piece.d)
952 {}
953 
954 //---------------------------------------------------------------------------------------------------------------------
operator =(VAbstractPiece && piece)955 VAbstractPiece &VAbstractPiece::operator=(VAbstractPiece &&piece) Q_DECL_NOTHROW
956 {
957     std::swap(d, piece.d);
958     return *this;
959 }
960 #endif
961 
962 //---------------------------------------------------------------------------------------------------------------------
~VAbstractPiece()963 VAbstractPiece::~VAbstractPiece()
964 {}
965 
966 //---------------------------------------------------------------------------------------------------------------------
GetName() const967 QString VAbstractPiece::GetName() const
968 {
969     return d->m_name;
970 }
971 
972 //---------------------------------------------------------------------------------------------------------------------
SetName(const QString & value)973 void VAbstractPiece::SetName(const QString &value)
974 {
975     d->m_name = value;
976 }
977 
978 //---------------------------------------------------------------------------------------------------------------------
IsForbidFlipping() const979 bool VAbstractPiece::IsForbidFlipping() const
980 {
981     return d->m_forbidFlipping;
982 }
983 
984 //---------------------------------------------------------------------------------------------------------------------
SetForbidFlipping(bool value)985 void VAbstractPiece::SetForbidFlipping(bool value)
986 {
987     d->m_forbidFlipping = value;
988 
989     if (value)
990     {
991         SetForceFlipping(not value);
992     }
993 }
994 
995 //---------------------------------------------------------------------------------------------------------------------
IsForceFlipping() const996 bool VAbstractPiece::IsForceFlipping() const
997 {
998     return d->m_forceFlipping;
999 }
1000 
1001 //---------------------------------------------------------------------------------------------------------------------
SetForceFlipping(bool value)1002 void VAbstractPiece::SetForceFlipping(bool value)
1003 {
1004     d->m_forceFlipping = value;
1005 
1006     if (value)
1007     {
1008         SetForbidFlipping(not value);
1009     }
1010 }
1011 
1012 //---------------------------------------------------------------------------------------------------------------------
IsSeamAllowance() const1013 bool VAbstractPiece::IsSeamAllowance() const
1014 {
1015     return d->m_seamAllowance;
1016 }
1017 
1018 //---------------------------------------------------------------------------------------------------------------------
SetSeamAllowance(bool value)1019 void VAbstractPiece::SetSeamAllowance(bool value)
1020 {
1021     d->m_seamAllowance = value;
1022 }
1023 
1024 //---------------------------------------------------------------------------------------------------------------------
IsSeamAllowanceBuiltIn() const1025 bool VAbstractPiece::IsSeamAllowanceBuiltIn() const
1026 {
1027     return d->m_seamAllowanceBuiltIn;
1028 }
1029 
1030 //---------------------------------------------------------------------------------------------------------------------
SetSeamAllowanceBuiltIn(bool value)1031 void VAbstractPiece::SetSeamAllowanceBuiltIn(bool value)
1032 {
1033     d->m_seamAllowanceBuiltIn = value;
1034 }
1035 
1036 //---------------------------------------------------------------------------------------------------------------------
IsHideMainPath() const1037 bool VAbstractPiece::IsHideMainPath() const
1038 {
1039     return d->m_hideMainPath;
1040 }
1041 
1042 //---------------------------------------------------------------------------------------------------------------------
SetHideMainPath(bool value)1043 void VAbstractPiece::SetHideMainPath(bool value)
1044 {
1045     d->m_hideMainPath = value;
1046 }
1047 
1048 //---------------------------------------------------------------------------------------------------------------------
GetSAWidth() const1049 qreal VAbstractPiece::GetSAWidth() const
1050 {
1051     return d->m_width;
1052 }
1053 
1054 //---------------------------------------------------------------------------------------------------------------------
SetSAWidth(qreal value)1055 void VAbstractPiece::SetSAWidth(qreal value)
1056 {
1057     value >= 0 ? d->m_width = value : d->m_width = 0;
1058 }
1059 
1060 //---------------------------------------------------------------------------------------------------------------------
Equidistant(QVector<VSAPoint> points,qreal width,const QString & name)1061 QVector<QPointF> VAbstractPiece::Equidistant(QVector<VSAPoint> points, qreal width, const QString &name)
1062 {
1063     if (width < 0)
1064     {
1065         qDebug()<<"Width < 0.";
1066         return QVector<QPointF>();
1067     }
1068     width = qMax(width, VSAPoint::minSAWidth);
1069 
1070 //    DumpVector(points, QStringLiteral("input.json.XXXXXX")); // Uncomment for dumping test data
1071 
1072     // Fix distorsion. Must be done before the correction
1073     points = CorrectPathDistortion(points);
1074 
1075     points = CorrectEquidistantPoints(points);
1076     if ( points.size() < 3 )
1077     {
1078         const QString errorMsg = tr("Piece '%1'. Not enough points to build seam allowance.").arg(name);
1079         VAbstractApplication::VApp()->IsPedantic() ? throw VException(errorMsg) :
1080                                               qWarning() << VAbstractValApplication::warningMessageSignature + errorMsg;
1081         return QVector<QPointF>();
1082     }
1083 
1084     if (points.last().toPoint() != points.first().toPoint())
1085     {
1086         points.append(points.at(0));// Should be always closed
1087     }
1088 
1089     bool needRollback = false; // no need for rollback
1090     QVector<VRawSAPoint> ekvPoints;
1091     for (qint32 i = 0; i < points.size(); ++i )
1092     {
1093         if ( i == 0)
1094         {//first point
1095             ekvPoints = EkvPoint(ekvPoints, points.at(points.size()-2), points.at(points.size()-1), points.at(1),
1096                                  points.at(0), width, &needRollback);
1097             continue;
1098         }
1099 
1100         if (i == points.size()-1)
1101         {//last point
1102             if (not ekvPoints.isEmpty())
1103             {
1104                 ekvPoints.append(ekvPoints.first());
1105             }
1106             continue;
1107         }
1108         //points in the middle of polyline
1109         ekvPoints = EkvPoint(ekvPoints, points.at(i-1), points.at(i), points.at(i+1), points.at(i), width);
1110     }
1111 
1112     if (needRollback)
1113     {
1114         QT_WARNING_PUSH
1115         QT_WARNING_DISABLE_GCC("-Wswitch-default")
1116         // This check helps to find missed angle types in the switch
1117         Q_STATIC_ASSERT_X(static_cast<int>(PieceNodeAngle::LAST_ONE_DO_NOT_USE) == 7, "Not all types were handled.");
1118         switch (points.last().GetAngleType())
1119         {
1120             case PieceNodeAngle::LAST_ONE_DO_NOT_USE:
1121             case PieceNodeAngle::ByFirstEdgeRightAngle:
1122                 Q_UNREACHABLE(); //-V501
1123                 break;
1124             case PieceNodeAngle::ByLength:
1125             case PieceNodeAngle::ByLengthCurve:
1126                 RollbackByLength(ekvPoints, points, width);
1127                 break;
1128             case PieceNodeAngle::ByFirstEdgeSymmetry:
1129                 RollbackByFirstEdgeSymmetry(ekvPoints, points, width);
1130                 break;
1131             case PieceNodeAngle::BySecondEdgeSymmetry:
1132                 RollbackBySecondEdgeSymmetry(ekvPoints, points, width);
1133                 break;
1134             case PieceNodeAngle::ByPointsIntersection:
1135                 RollbackByPointsIntersection(ekvPoints, points, width);
1136                 break;
1137             case PieceNodeAngle::BySecondEdgeRightAngle:
1138                 RollbackBySecondEdgeRightAngle(ekvPoints, points, width);
1139                 break;
1140         }
1141         QT_WARNING_POP
1142     }
1143 
1144 //    Uncomment for debug
1145 //    QVector<QPointF> cleaned;
1146 //    cleaned.reserve(ekvPoints.size());
1147 //    for (auto &point : ekvPoints)
1148 //    {
1149 //        cleaned.append(point);
1150 //    }
1151 
1152     const bool removeFirstAndLast = false;
1153     ekvPoints = RemoveDublicates(ekvPoints, removeFirstAndLast);
1154     QVector<QPointF> cleaned = CheckLoops(ekvPoints);//Result path can contain loops
1155     cleaned = CorrectEquidistantPoints(cleaned, removeFirstAndLast);
1156     cleaned = CorrectPathDistortion(cleaned);
1157 //    DumpVector(cleaned, QStringLiteral("output.json.XXXXXX")); // Uncomment for dumping test data
1158     return cleaned;
1159 }
1160 
1161 //---------------------------------------------------------------------------------------------------------------------
SumTrapezoids(const QVector<QPointF> & points)1162 qreal VAbstractPiece::SumTrapezoids(const QVector<QPointF> &points)
1163 {
1164     // Calculation a polygon area through the sum of the areas of trapezoids
1165     qreal s, res = 0;
1166     const int n = points.size();
1167 
1168     if(n > 2)
1169     {
1170         for (int i = 0; i < n; ++i)
1171         {
1172             if (i == 0)
1173             {
1174                 //if i == 0, then y[i-1] replace on y[n-1]
1175                 s = points.at(i).x()*(points.at(n-1).y() - points.at(i+1).y());
1176                 res += s;
1177             }
1178             else
1179             {
1180                 if (i == n-1)
1181                 {
1182                     // if i == n-1, then y[i+1] replace on y[0]
1183                     s = points.at(i).x()*(points.at(i-1).y() - points.at(0).y());
1184                     res += s;
1185                 }
1186                 else
1187                 {
1188                     s = points.at(i).x()*(points.at(i-1).y() - points.at(i+1).y());
1189                     res += s;
1190                 }
1191             }
1192         }
1193     }
1194     return res;
1195 }
1196 
1197 //---------------------------------------------------------------------------------------------------------------------
CheckLoops(const QVector<QPointF> & points)1198 QVector<QPointF> VAbstractPiece::CheckLoops(const QVector<QPointF> &points)
1199 {
1200     QVector<VRawSAPoint> rawPath;
1201     rawPath.reserve(points.size());
1202     for (auto &point : points)
1203     {
1204         rawPath.append(point);
1205     }
1206 
1207     return CheckLoops(rawPath);
1208 }
1209 
1210 //---------------------------------------------------------------------------------------------------------------------
1211 /**
1212  * @brief CheckLoops seek and delete loops in equidistant.
1213  * @param points vector of points of equidistant.
1214  * @return vector of points of equidistant.
1215  */
CheckLoops(const QVector<VRawSAPoint> & points)1216 auto VAbstractPiece::CheckLoops(const QVector<VRawSAPoint> &points) -> QVector<QPointF>
1217 {
1218 //    DumpVector(points, QStringLiteral("input.json.XXXXXX")); // Uncomment for dumping test data
1219 
1220     /*If we got less than 4 points no need seek loops.*/
1221     if (points.size() < 4)
1222     {
1223         return CleanLoopArtifacts(points);
1224     }
1225 
1226     bool loopFound = false;
1227 
1228     auto CheckLoop = [&loopFound](const QVector<VRawSAPoint> &points)
1229     {
1230         loopFound = false;
1231 
1232         const bool pathClosed = (points.first() == points.last());
1233 
1234         QVector<VRawSAPoint> ekvPoints;
1235         ekvPoints.reserve(points.size());
1236 
1237         qint32 i;
1238         for (i = 0; i < points.size(); ++i)
1239         {
1240             /*Last three points no need to check.*/
1241             /*Triangle can not contain a loop*/
1242             if (loopFound || i > points.size()-4)
1243             {
1244                 ekvPoints.append(points.at(i));
1245                 continue;
1246             }
1247 
1248             enum LoopIntersectType { NoIntersection, BoundedIntersection, ParallelIntersection };
1249 
1250             QPointF crosPoint;
1251             LoopIntersectType status = NoIntersection;
1252             const QLineF line1(points.at(i), points.at(i+1));
1253 
1254             const int limit = pathClosed && i == 0 ? 2 : 1;
1255             qint32 j;
1256             for (j = i+2; j < points.size()-limit; ++j)
1257             {
1258                 QLineF line2(points.at(j), points.at(j+1));
1259 
1260                 const QLineF::IntersectType intersect = Intersects(line1, line2, &crosPoint);
1261                 if (intersect == QLineF::NoIntersection)
1262                 { // According to the documentation QLineF::NoIntersection indicates that the lines do not intersect;
1263                   // i.e. they are parallel. But parallel also mean they can be on the same line.
1264                   // Method IsLineSegmentOnLineSegment will check it.
1265                     if (VGObject::IsLineSegmentOnLineSegment(line1, line2))
1266                     {// Now we really sure that segments are on the same line and have real intersections.
1267                         status = ParallelIntersection;
1268                         break;
1269                     }
1270                 }
1271                 else if (intersect == QLineF::BoundedIntersection)
1272                 {
1273                     status = BoundedIntersection;
1274                     break;
1275                 }
1276             }
1277 
1278             switch (status)
1279             {
1280                 case ParallelIntersection:
1281                     /*We have found a loop.*/
1282                     ekvPoints.append(points.at(i));
1283                     ekvPoints.append(points.at(j+1));
1284                     i = j+1; // Skip a loop
1285                     loopFound = true;
1286                     break;
1287                 case BoundedIntersection:
1288                     ekvPoints.append(points.at(i));
1289                     ekvPoints.append(crosPoint);
1290                     i = j;
1291                     loopFound = true;
1292                     break;
1293                 case NoIntersection:
1294                     /*We have not found loop.*/
1295                     ekvPoints.append(points.at(i));
1296                     break;
1297                 default:
1298                     break;
1299             }
1300         }
1301         return ekvPoints;
1302     };
1303 
1304     QVector<VRawSAPoint> ekvPoints = points;
1305     qint32 i;
1306     const int maxLoops = 10000; // limit number of loops to be removed
1307 
1308     for (i = 0; i < maxLoops; ++i)
1309     {
1310         ekvPoints = CheckLoop(ekvPoints);
1311         if (not loopFound)
1312         {
1313             break;
1314         }
1315     }
1316 
1317     const QVector<QPointF> cleaned = CleanLoopArtifacts(ekvPoints);
1318 //    DumpVector(cleaned, QStringLiteral("output.json.XXXXXX")); // Uncomment for dumping test data
1319     return cleaned;
1320 }
1321 
1322 //---------------------------------------------------------------------------------------------------------------------
1323 /**
1324  * @brief EkvPoint return seam aloowance points in place of intersection two edges. Last points of two edges should be
1325  * equal.
1326  * @param width global seam allowance width.
1327  * @return seam aloowance points.
1328  */
EkvPoint(QVector<VRawSAPoint> points,const VSAPoint & p1Line1,const VSAPoint & p2Line1,const VSAPoint & p1Line2,const VSAPoint & p2Line2,qreal width,bool * needRollback)1329 QVector<VRawSAPoint> VAbstractPiece::EkvPoint(QVector<VRawSAPoint> points, const VSAPoint &p1Line1,
1330                                               const VSAPoint &p2Line1, const VSAPoint &p1Line2, const VSAPoint &p2Line2,
1331                                               qreal width, bool *needRollback)
1332 {
1333     if (width < 0)
1334     { // width can't be < 0
1335         return QVector<VRawSAPoint>();
1336     }
1337 
1338     width = qMax(width, VSAPoint::minSAWidth);
1339 
1340     if (p2Line1 != p2Line2)
1341     {
1342         qDebug()<<"Last points of two lines must be equal.";
1343         return QVector<VRawSAPoint>(); // Wrong edges
1344     }
1345 
1346     const QLineF bigLine1 = ParallelLine(p1Line1, p2Line1, width );
1347     const QLineF bigLine2 = ParallelLine(p2Line2, p1Line2, width );
1348 
1349     if (VFuzzyComparePoints(bigLine1.p2(), bigLine2.p1()))
1350     {
1351         points.append(bigLine1.p2());
1352         return points;
1353     }
1354 
1355     QPointF crosPoint;
1356     const QLineF::IntersectType type = Intersects(bigLine1, bigLine2, &crosPoint );
1357 
1358     switch (type)
1359     {// There are at least three big cases
1360         case (QLineF::BoundedIntersection):
1361             // The easiest, real intersection
1362             points.append(crosPoint);
1363             return points;
1364         case (QLineF::UnboundedIntersection):
1365         { // Most common case
1366             /* Case when a path has point on line (both segments lie on the same line) and seam allowance creates
1367              * prong. */
1368             auto IsOnLine = [](const QPointF &base, const QPointF &sp1, const QPointF &sp2, qreal accuracy)
1369             {
1370                 if (not VFuzzyComparePoints(base, sp1))
1371                 {
1372                     return VGObject::IsPointOnLineviaPDP(sp2, base, sp1, accuracy);
1373                 }
1374 
1375                 if (not VFuzzyComparePoints(base, sp2))
1376                 {
1377                     return VGObject::IsPointOnLineviaPDP(sp1, base, sp2, accuracy);
1378                 }
1379                 return true;
1380             };
1381             if (VGObject::IsPointOnLineSegment(p2Line1, p1Line1, p1Line2, ToPixel(0.5, Unit::Mm)) &&
1382                     IsOnLine(p2Line1, bigLine1.p2(), bigLine2.p1(), ToPixel(0.5, Unit::Mm)))
1383             {
1384                 points.append(bigLine1.p2());
1385                 points.append(bigLine2.p1());
1386                 return points;
1387             }
1388 
1389             const qreal localWidth = p2Line1.MaxLocalSA(width);
1390             QLineF line( p2Line1, crosPoint );
1391 
1392             // Checking two subcases
1393             const QLineF b1 = BisectorLine(p1Line1, p2Line1, p1Line2);
1394             const QLineF b2 = BisectorLine(bigLine1.p1(), crosPoint, bigLine2.p2());
1395 
1396             const qreal angle = AngleBetweenBisectors(b1, b2);
1397 
1398             // Comparison bisector angles helps to find direction
1399             if (angle < 135
1400                 || VFuzzyComparePossibleNulls(angle, 135.0))// Go in a same direction
1401             {//Regular equdistant case
1402 QT_WARNING_PUSH
1403 QT_WARNING_DISABLE_GCC("-Wswitch-default")
1404                 // This check helps to find missed angle types in the switch
1405                 Q_STATIC_ASSERT_X(static_cast<int>(PieceNodeAngle::LAST_ONE_DO_NOT_USE) == 7,
1406                                   "Not all types were handled.");
1407                 switch (p2Line1.GetAngleType())
1408                 {
1409                     case PieceNodeAngle::LAST_ONE_DO_NOT_USE:
1410                         Q_UNREACHABLE(); //-V501
1411                         break;
1412                     case PieceNodeAngle::ByLength:
1413                     case PieceNodeAngle::ByLengthCurve:
1414                         return AngleByLength(points, p1Line1, p2Line1, p1Line2, bigLine1, crosPoint, bigLine2, p2Line1,
1415                                              width, needRollback);
1416                     case PieceNodeAngle::ByPointsIntersection:
1417                         return AngleByIntersection(points, p1Line1, p2Line1, p1Line2, bigLine1, crosPoint, bigLine2,
1418                                                    p2Line1, width, needRollback);
1419                     case PieceNodeAngle::ByFirstEdgeSymmetry:
1420                         return AngleByFirstSymmetry(points, p1Line1, p2Line1, p1Line2, bigLine1, crosPoint, bigLine2,
1421                                                     p2Line1, width, needRollback);
1422                     case PieceNodeAngle::BySecondEdgeSymmetry:
1423                         return AngleBySecondSymmetry(points, p1Line1, p2Line1, p1Line2, bigLine1, crosPoint, bigLine2,
1424                                                      p2Line1, width, needRollback);
1425                     case PieceNodeAngle::ByFirstEdgeRightAngle:
1426                         return AngleByFirstRightAngle(points, p1Line1, p2Line1, p1Line2, bigLine1, crosPoint, bigLine2,
1427                                                       p2Line1, width, needRollback);
1428                     case PieceNodeAngle::BySecondEdgeRightAngle:
1429                         return AngleBySecondRightAngle(points, p1Line1, p2Line1, p1Line2, bigLine1, crosPoint, bigLine2,
1430                                                        p2Line1, width, needRollback);
1431                 }
1432 QT_WARNING_POP
1433             }
1434             else
1435             { // Different directions
1436                 QLineF bisector(p2Line1, p1Line1);
1437                 bisector.setAngle(b1.angle());
1438 
1439                 const qreal result1 = PointPosition(bisector.p2(), QLineF(p1Line1, p2Line1));
1440                 const qreal result2 = PointPosition(bisector.p2(), QLineF(p2Line2, p1Line2));
1441 
1442                 if ((result1 < 0 || qFuzzyIsNull(result1)) && (result2 < 0 || qFuzzyIsNull(result2)))
1443                 {// Dart case. A bisector watches outside.
1444                     QLineF edge1(p1Line1, p2Line1);
1445                     QLineF edge2(p1Line2, p2Line2);
1446 
1447                     if (qAbs(edge1.length() - edge2.length()) <= qMax(edge1.length(), edge2.length())*0.2)
1448                     {
1449                         // Classic dart must be symmetrical.
1450                         // In some cases a point still valid, but ignore if going outside of an equdistant.
1451 
1452                         const QLineF bigEdge = ParallelLine(p1Line1, p1Line2, localWidth );
1453                         QPointF px;
1454                         const QLineF::IntersectType type = Intersects(bigEdge, line, &px);
1455                         if (type != QLineF::BoundedIntersection && line.length() < QLineF(p2Line1, px).length())
1456                         {
1457                             points.append(crosPoint);
1458                             return points;
1459                         }
1460                     }
1461                     else
1462                     { // Just an acute angle with big seam allowance
1463                         if (IsSameDirection(bigLine2.p1(), bigLine2.p2(), crosPoint))
1464                         {
1465                             QLineF loop(crosPoint, bigLine1.p1());
1466                             loop.setAngle(loop.angle() + 180);
1467                             loop.setLength(accuracyPointOnLine*2.);
1468                             points.append(loop.p2());
1469                             points.append(crosPoint);
1470 
1471                             loop = QLineF(crosPoint, bigLine1.p1());
1472                             loop.setLength(loop.length() + localWidth*2.);
1473                             points.append(VRawSAPoint(loop.p2(), true));
1474                         }
1475 
1476                         return points;
1477                     }
1478                 }
1479                 else
1480                 { // New subcase. This is not a dart. An angle is acute and bisector watch inside.
1481                     const qreal result1 = PointPosition(crosPoint, QLineF(p1Line1, p2Line1));
1482                     const qreal result2 = PointPosition(crosPoint, QLineF(p2Line2, p1Line2));
1483 
1484                     if ((result1 < 0 || qFuzzyIsNull(result1)) && (result2 < 0 || qFuzzyIsNull(result2)))
1485                     {// The cross point is still outside of a piece
1486                         if (line.length() >= localWidth)
1487                         {
1488                             points.append(crosPoint);
1489                             return points;
1490                         }
1491                         else
1492                         {// but not enough far, fix it
1493                             line.setLength(localWidth);
1494                             points.append(line.p2());
1495                             return points;
1496                         }
1497                     }
1498                     else
1499                     {// Wrong cross point, probably inside of a piece. Manually creating correct seam allowance
1500                         const QLineF bigEdge = SimpleParallelLine(bigLine1.p2(), bigLine2.p1(), localWidth );
1501                         points.append(bigEdge.p1());
1502                         points.append(bigEdge.p2());
1503                         return points;
1504                     }
1505                 }
1506             }
1507             break;
1508         }
1509         case (QLineF::NoIntersection):
1510             /*If we have correct lines this means lines lie on a line or parallel.*/
1511             points.append(bigLine1.p2());
1512             points.append(bigLine2.p1()); // Second point for parallel line
1513             return points;
1514         default:
1515             break;
1516     }
1517     return points;
1518 }
1519 
1520 //---------------------------------------------------------------------------------------------------------------------
ParallelLine(const VSAPoint & p1,const VSAPoint & p2,qreal width)1521 QLineF VAbstractPiece::ParallelLine(const VSAPoint &p1, const VSAPoint &p2, qreal width)
1522 {
1523     return QLineF(SingleParallelPoint(p1, p2, 90, p1.GetSAAfter(width)),
1524                   SingleParallelPoint(p2, p1, -90, p2.GetSABefore(width)));
1525 }
1526 
1527 //---------------------------------------------------------------------------------------------------------------------
IsAllowanceValid(const QVector<QPointF> & base,const QVector<QPointF> & allowance)1528 bool VAbstractPiece::IsAllowanceValid(const QVector<QPointF> &base, const QVector<QPointF> &allowance)
1529 {
1530     if (base.size() < 3 || allowance.size() < 3)
1531     {
1532         return false; // Not enough data
1533     }
1534 
1535 //    DumpVector(base); // Uncomment for dumping test data
1536 //    DumpVector(allowance); // Uncomment for dumping test data
1537 
1538     // First check direction
1539     const qreal baseDirection = VPiece::SumTrapezoids(base);
1540     const qreal allowanceDirection = VPiece::SumTrapezoids(allowance);
1541 
1542     if (baseDirection >= 0 || allowanceDirection >= 0)
1543     {
1544         return false; // Wrong direction
1545     }
1546 
1547     return IsInsidePolygon(base, allowance);
1548 }
1549 
1550 //---------------------------------------------------------------------------------------------------------------------
IsEkvPointOnLine(const QPointF & iPoint,const QPointF & prevPoint,const QPointF & nextPoint)1551 bool VAbstractPiece::IsEkvPointOnLine(const QPointF &iPoint, const QPointF &prevPoint, const QPointF &nextPoint)
1552 {
1553     return VGObject::IsPointOnLineviaPDP(iPoint, prevPoint, nextPoint, accuracyPointOnLine/4.);
1554 }
1555 
1556 //---------------------------------------------------------------------------------------------------------------------
IsEkvPointOnLine(const VSAPoint & iPoint,const VSAPoint & prevPoint,const VSAPoint & nextPoint)1557 bool VAbstractPiece::IsEkvPointOnLine(const VSAPoint &iPoint, const VSAPoint &prevPoint, const VSAPoint &nextPoint)
1558 {
1559     // See bug #671
1560     const qreal tmpWidth = 10;
1561     const QLineF bigLine1 = ParallelLine(prevPoint, iPoint, tmpWidth );
1562     const QLineF bigLine2 = ParallelLine(iPoint, nextPoint, tmpWidth );
1563 
1564     bool seamOnLine = VGObject::IsPointOnLineviaPDP(iPoint, prevPoint, nextPoint);
1565     bool sa1OnLine = VGObject::IsPointOnLineviaPDP(bigLine1.p2(), bigLine1.p1(), bigLine2.p2());
1566     bool sa2OnLine = VGObject::IsPointOnLineviaPDP(bigLine2.p1(), bigLine1.p1(), bigLine2.p2());
1567     bool saDiff = qAbs(prevPoint.GetSAAfter(tmpWidth) - nextPoint.GetSABefore(tmpWidth)) < accuracyPointOnLine;
1568 
1569     // left point that splits a curve
1570     bool curve = (prevPoint.GetAngleType() == PieceNodeAngle::ByLengthCurve &&
1571                   iPoint.GetAngleType() == PieceNodeAngle::ByLengthCurve) ||
1572                  (nextPoint.GetAngleType() == PieceNodeAngle::ByLengthCurve &&
1573                   iPoint.GetAngleType() == PieceNodeAngle::ByLengthCurve);
1574 
1575     return seamOnLine && sa1OnLine && sa2OnLine && saDiff && not curve;
1576 }
1577 
1578 //---------------------------------------------------------------------------------------------------------------------
GetMx() const1579 qreal VAbstractPiece::GetMx() const
1580 {
1581     return d->m_mx;
1582 }
1583 
1584 //---------------------------------------------------------------------------------------------------------------------
SetMx(qreal value)1585 void VAbstractPiece::SetMx(qreal value)
1586 {
1587     d->m_mx = value;
1588 }
1589 
1590 //---------------------------------------------------------------------------------------------------------------------
GetMy() const1591 qreal VAbstractPiece::GetMy() const
1592 {
1593     return d->m_my;
1594 }
1595 
1596 //---------------------------------------------------------------------------------------------------------------------
SetMy(qreal value)1597 void VAbstractPiece::SetMy(qreal value)
1598 {
1599     d->m_my = value;
1600 }
1601 
1602 //---------------------------------------------------------------------------------------------------------------------
GetPriority() const1603 uint VAbstractPiece::GetPriority() const
1604 {
1605     return d->m_priority;
1606 }
1607 
1608 //---------------------------------------------------------------------------------------------------------------------
SetPriority(uint value)1609 void VAbstractPiece::SetPriority(uint value)
1610 {
1611     d->m_priority = value;
1612 }
1613 
1614 //---------------------------------------------------------------------------------------------------------------------
GetSABefore(qreal width) const1615 qreal VSAPoint::GetSABefore(qreal width) const
1616 {
1617     if (m_before < 0)
1618     {
1619         return width;
1620     }
1621     return qMax(m_before, minSAWidth);
1622 }
1623 
1624 //---------------------------------------------------------------------------------------------------------------------
GetSAAfter(qreal width) const1625 qreal VSAPoint::GetSAAfter(qreal width) const
1626 {
1627     if (m_after < 0)
1628     {
1629         return width;
1630     }
1631     return qMax(m_after, minSAWidth);
1632 }
1633 
1634 //---------------------------------------------------------------------------------------------------------------------
MaxLocalSA(qreal width) const1635 qreal VSAPoint::MaxLocalSA(qreal width) const
1636 {
1637     return qMax(GetSAAfter(width), GetSABefore(width));
1638 }
1639 
1640 //---------------------------------------------------------------------------------------------------------------------
PassmarkLength(qreal width) const1641 qreal VSAPoint::PassmarkLength(qreal width) const
1642 {
1643     if (not m_manualPassmarkLength)
1644     {
1645         qreal passmarkLength = MaxLocalSA(width) * passmarkFactor;
1646         passmarkLength = qMin(passmarkLength, maxPassmarkLength);
1647         return passmarkLength;
1648     }
1649 
1650     return m_passmarkLength;
1651 }
1652 
1653 //---------------------------------------------------------------------------------------------------------------------
toJson() const1654 QJsonObject VSAPoint::toJson() const
1655 {
1656     QJsonObject pointObject;
1657     pointObject[QLatin1String("type")] = "VSAPoint";
1658     pointObject[QLatin1String("x")] = x();
1659     pointObject[QLatin1String("y")] = y();
1660 
1661     if (not VFuzzyComparePossibleNulls(m_before, -1))
1662     {
1663         pointObject[QLatin1String("saBefore")] = m_before;
1664     }
1665 
1666     if (not VFuzzyComparePossibleNulls(m_after, -1))
1667     {
1668         pointObject[QLatin1String("saAfter")] = m_after;
1669     }
1670 
1671     if (m_angle != PieceNodeAngle::ByLength)
1672     {
1673         pointObject[QLatin1String("angle")] = static_cast<int>(m_angle);
1674     }
1675 
1676     return pointObject;
1677 }
1678 
1679 //---------------------------------------------------------------------------------------------------------------------
1680 // Because artificial loop can lead to wrong clipping we must rollback current seam allowance points
RollbackSeamAllowance(QVector<VRawSAPoint> points,const QLineF & cuttingEdge,bool * success)1681 QVector<VRawSAPoint> VAbstractPiece::RollbackSeamAllowance(QVector<VRawSAPoint> points, const QLineF &cuttingEdge,
1682                                                            bool *success)
1683 {
1684     *success = false;
1685     QVector<VRawSAPoint> clipped;
1686     clipped.reserve(points.count()+1);
1687     for (int i = points.count()-1; i > 0; --i)
1688     {
1689         QLineF segment(points.at(i), points.at(i-1));
1690         QPointF crosPoint;
1691         const QLineF::IntersectType type = Intersects(cuttingEdge, segment, &crosPoint);
1692 
1693         if (type != QLineF::NoIntersection
1694                 && VGObject::IsPointOnLineSegment(crosPoint, segment.p1(), segment.p2())
1695                 && IsSameDirection(cuttingEdge.p2(), cuttingEdge.p1(), crosPoint))
1696         {
1697             clipped.append(crosPoint);
1698             for (int j=i-1; j>=0; --j)
1699             {
1700                 clipped.append(points.at(j));
1701             }
1702             points = Reverse(clipped);
1703             *success = true;
1704             break;
1705         }
1706     }
1707 
1708     if (not *success && points.size() > 1)
1709     {
1710         QPointF crosPoint;
1711         QLineF secondLast(points.at(points.size()-2), points.at(points.size()-1));
1712         QLineF::IntersectType type = Intersects(secondLast, cuttingEdge, &crosPoint);
1713 
1714         if (type != QLineF::NoIntersection && IsOutsidePoint(secondLast.p1(), secondLast.p2(), crosPoint))
1715         {
1716             points.append(crosPoint);
1717             *success = true;
1718         }
1719     }
1720 
1721     return points;
1722 }
1723 
1724 
1725 //---------------------------------------------------------------------------------------------------------------------
IsItemContained(const QRectF & parentBoundingRect,const QVector<QPointF> & shape,qreal & dX,qreal & dY)1726 bool VAbstractPiece::IsItemContained(const QRectF &parentBoundingRect, const QVector<QPointF> &shape, qreal &dX,
1727                                      qreal &dY)
1728 {
1729     dX = 0;
1730     dY = 0;
1731     // single point differences
1732     bool bInside = true;
1733 
1734     for (auto p : shape)
1735     {
1736         qreal dPtX = 0;
1737         qreal dPtY = 0;
1738         if (not parentBoundingRect.contains(p))
1739         {
1740             if (p.x() < parentBoundingRect.left())
1741             {
1742                 dPtX = parentBoundingRect.left() - p.x();
1743             }
1744             else if (p.x() > parentBoundingRect.right())
1745             {
1746                 dPtX = parentBoundingRect.right() - p.x();
1747             }
1748 
1749             if (p.y() < parentBoundingRect.top())
1750             {
1751                 dPtY = parentBoundingRect.top() - p.y();
1752             }
1753             else if (p.y() > parentBoundingRect.bottom())
1754             {
1755                 dPtY = parentBoundingRect.bottom() - p.y();
1756             }
1757 
1758             if (qAbs(dPtX) > qAbs(dX))
1759             {
1760                 dX = dPtX;
1761             }
1762 
1763             if (qAbs(dPtY) > qAbs(dY))
1764             {
1765                 dY = dPtY;
1766             }
1767 
1768             bInside = false;
1769         }
1770     }
1771     return bInside;
1772 }
1773 
1774 //---------------------------------------------------------------------------------------------------------------------
CorrectPosition(const QRectF & parentBoundingRect,QVector<QPointF> points)1775 QVector<QPointF> VAbstractPiece::CorrectPosition(const QRectF &parentBoundingRect, QVector<QPointF> points)
1776 {
1777     qreal dX = 0;
1778     qreal dY = 0;
1779     if (not IsItemContained(parentBoundingRect, points, dX, dY))
1780     {
1781         for (int i =0; i < points.size(); ++i)
1782         {
1783             points[i] = QPointF(points.at(i).x() + dX, points.at(i).y() + dY);
1784         }
1785     }
1786     return points;
1787 }
1788 
1789 //---------------------------------------------------------------------------------------------------------------------
FindGrainlineGeometry(const VGrainlineData & geom,const VContainer * pattern,qreal & length,qreal & rotationAngle,QPointF & pos)1790 bool VAbstractPiece::FindGrainlineGeometry(const VGrainlineData& geom, const VContainer *pattern, qreal &length,
1791                                            qreal &rotationAngle, QPointF &pos)
1792 {
1793     SCASSERT(pattern != nullptr)
1794 
1795     const quint32 topPin = geom.TopPin();
1796     const quint32 bottomPin = geom.BottomPin();
1797 
1798     if (topPin != NULL_ID && bottomPin != NULL_ID)
1799     {
1800         try
1801         {
1802             const auto topPinPoint = pattern->GeometricObject<VPointF>(topPin);
1803             const auto bottomPinPoint = pattern->GeometricObject<VPointF>(bottomPin);
1804 
1805             QLineF grainline(static_cast<QPointF>(*bottomPinPoint), static_cast<QPointF>(*topPinPoint));
1806             length = grainline.length();
1807             rotationAngle = grainline.angle();
1808 
1809             if (not VFuzzyComparePossibleNulls(rotationAngle, 0))
1810             {
1811                 grainline.setAngle(0);
1812             }
1813 
1814             pos = grainline.p1();
1815             rotationAngle = qDegreesToRadians(rotationAngle);
1816 
1817             return true;
1818         }
1819         catch(const VExceptionBadId &)
1820         {
1821             // do nothing.
1822         }
1823     }
1824 
1825     try
1826     {
1827         Calculator cal1;
1828         rotationAngle = cal1.EvalFormula(pattern->DataVariables(), geom.GetRotation());
1829         rotationAngle = qDegreesToRadians(rotationAngle);
1830 
1831         Calculator cal2;
1832         length = cal2.EvalFormula(pattern->DataVariables(), geom.GetLength());
1833         length = ToPixel(length, *pattern->GetPatternUnit());
1834     }
1835     catch(qmu::QmuParserError &e)
1836     {
1837         Q_UNUSED(e);
1838         return false;
1839     }
1840 
1841     const quint32 centerPin = geom.CenterPin();
1842     if (centerPin != NULL_ID)
1843     {
1844         try
1845         {
1846             const auto centerPinPoint = pattern->GeometricObject<VPointF>(centerPin);
1847 
1848             QLineF grainline(centerPinPoint->x(), centerPinPoint->y(),
1849                              centerPinPoint->x() + length / 2.0, centerPinPoint->y());
1850 
1851             grainline.setAngle(qRadiansToDegrees(rotationAngle));
1852             grainline = QLineF(grainline.p2(), grainline.p1());
1853             grainline.setLength(length);
1854 
1855             pos = grainline.p2();
1856         }
1857         catch(const VExceptionBadId &)
1858         {
1859             pos = geom.GetPos();
1860         }
1861     }
1862     else
1863     {
1864         pos = geom.GetPos();
1865     }
1866     return true;
1867 }
1868 
1869 //---------------------------------------------------------------------------------------------------------------------
GrainlinePoints(const VGrainlineData & geom,const VContainer * pattern,const QRectF & boundingRect,qreal & dAng)1870 QVector<QPointF> VAbstractPiece::GrainlinePoints(const VGrainlineData &geom, const VContainer *pattern,
1871                                                  const QRectF &boundingRect, qreal &dAng)
1872 {
1873     SCASSERT(pattern != nullptr)
1874 
1875     QPointF pt1;
1876     qreal dLen = 0;
1877     if ( not FindGrainlineGeometry(geom, pattern, dLen, dAng, pt1))
1878     {
1879         return QVector<QPointF>();
1880     }
1881 
1882     qreal rotation = dAng;
1883 
1884     QPointF pt2(pt1.x() + dLen * qCos(rotation), pt1.y() - dLen * qSin(rotation));
1885 
1886     const qreal dArrowLen = ToPixel(0.5, *pattern->GetPatternUnit());
1887     const qreal dArrowAng = M_PI/9;
1888 
1889     QVector<QPointF> v;
1890     v << pt1;
1891 
1892     if (geom.GetArrowType() != GrainlineArrowDirection::atFront)
1893     {
1894         v << QPointF(pt1.x() + dArrowLen * qCos(rotation + dArrowAng),
1895                      pt1.y() - dArrowLen * qSin(rotation + dArrowAng));
1896         v << QPointF(pt1.x() + dArrowLen * qCos(rotation - dArrowAng),
1897                      pt1.y() - dArrowLen * qSin(rotation - dArrowAng));
1898         v << pt1;
1899     }
1900 
1901     v << pt2;
1902 
1903     if (geom.GetArrowType() != GrainlineArrowDirection::atRear)
1904     {
1905         rotation += M_PI;
1906 
1907         v << QPointF(pt2.x() + dArrowLen * qCos(rotation + dArrowAng),
1908                      pt2.y() - dArrowLen * qSin(rotation + dArrowAng));
1909         v << QPointF(pt2.x() + dArrowLen * qCos(rotation - dArrowAng),
1910                      pt2.y() - dArrowLen * qSin(rotation - dArrowAng));
1911         v << pt2;
1912     }
1913 
1914     return CorrectPosition(boundingRect, v);
1915 }
1916 
1917 //---------------------------------------------------------------------------------------------------------------------
PainterPath(const QVector<QPointF> & points)1918 QPainterPath VAbstractPiece::PainterPath(const QVector<QPointF> &points)
1919 {
1920     QPainterPath path;
1921     path.setFillRule(Qt::WindingFill);
1922 
1923     if (not points.isEmpty())
1924     {
1925         path.moveTo(points.at(0));
1926         for (qint32 i = 1; i < points.count(); ++i)
1927         {
1928             path.lineTo(points.at(i));
1929         }
1930         path.lineTo(points.at(0));
1931     }
1932 
1933     return path;
1934 }
1935