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