1
/***************************************************************************
2
* Copyright (c) 2023 WandererFan <wandererfan@gmail.com> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
28
#include <Base/Console.h>
30
#include "PathBuilder.h"
33
using namespace TechDraw;
34
using namespace TechDrawGui;
37
QPainterPath PathBuilder::geomToPainterPath(BaseGeomPtr baseGeom, double rot) const
45
switch (baseGeom->getGeomType()) {
47
TechDraw::CirclePtr geom = std::static_pointer_cast<TechDraw::Circle>(baseGeom);
49
double x = geom->center.x - geom->radius;
50
double y = geom->center.y - geom->radius;
52
path.addEllipse(Rez::guiX(x), Rez::guiX(y), Rez::guiX(geom->radius * 2),
53
Rez::guiX(geom->radius * 2));//topleft@(x, y) radx, rady
56
TechDraw::AOCPtr geom = std::static_pointer_cast<TechDraw::AOC>(baseGeom);
57
if (baseGeom->getReversed()) {
58
path.moveTo(Rez::guiX(geom->endPnt.x), Rez::guiX(geom->endPnt.y));
59
pathArc(path, Rez::guiX(geom->radius), Rez::guiX(geom->radius), 0., geom->largeArc,
60
!geom->cw, Rez::guiX(geom->startPnt.x), Rez::guiX(geom->startPnt.y),
61
Rez::guiX(geom->endPnt.x), Rez::guiX(geom->endPnt.y));
64
path.moveTo(Rez::guiX(geom->startPnt.x), Rez::guiX(geom->startPnt.y));
65
pathArc(path, Rez::guiX(geom->radius), Rez::guiX(geom->radius), 0., geom->largeArc,
66
geom->cw, Rez::guiX(geom->endPnt.x), Rez::guiX(geom->endPnt.y),
67
Rez::guiX(geom->startPnt.x), Rez::guiX(geom->startPnt.y));
70
case TechDraw::ELLIPSE: {
71
TechDraw::AOEPtr geom = std::static_pointer_cast<TechDraw::AOE>(baseGeom);
73
// Calculate start and end points as ellipse with theta = 0 and pi
74
double startX = geom->center.x + geom->major * cos(geom->angle),
75
startY = geom->center.y + geom->major * sin(geom->angle),
76
endX = geom->center.x - geom->major * cos(geom->angle),
77
endY = geom->center.y - geom->major * sin(geom->angle);
79
pathArc(path, Rez::guiX(geom->major), Rez::guiX(geom->minor), geom->angle, false, false,
80
Rez::guiX(endX), Rez::guiX(endY), Rez::guiX(startX), Rez::guiX(startY));
82
pathArc(path, Rez::guiX(geom->major), Rez::guiX(geom->minor), geom->angle, false, false,
83
Rez::guiX(startX), Rez::guiX(startY), Rez::guiX(endX), Rez::guiX(endY));
85
case TechDraw::ARCOFELLIPSE: {
86
TechDraw::AOEPtr geom = std::static_pointer_cast<TechDraw::AOE>(baseGeom);
87
if (baseGeom->getReversed()) {
88
path.moveTo(Rez::guiX(geom->endPnt.x), Rez::guiX(geom->endPnt.y));
89
pathArc(path, Rez::guiX(geom->major), Rez::guiX(geom->minor), geom->angle,
90
geom->largeArc, !geom->cw, Rez::guiX(geom->startPnt.x),
91
Rez::guiX(geom->startPnt.y), Rez::guiX(geom->endPnt.x),
92
Rez::guiX(geom->endPnt.y));
95
path.moveTo(Rez::guiX(geom->startPnt.x), Rez::guiX(geom->startPnt.y));
96
pathArc(path, Rez::guiX(geom->major), Rez::guiX(geom->minor), geom->angle,
97
geom->largeArc, geom->cw, Rez::guiX(geom->endPnt.x),
98
Rez::guiX(geom->endPnt.y), Rez::guiX(geom->startPnt.x),
99
Rez::guiX(geom->startPnt.y));
102
case TechDraw::BEZIER: {
103
TechDraw::BezierSegmentPtr geom =
104
std::static_pointer_cast<TechDraw::BezierSegment>(baseGeom);
105
if (baseGeom->getReversed()) {
106
if (!geom->pnts.empty()) {
107
Base::Vector3d rStart = geom->pnts.back();
108
path.moveTo(Rez::guiX(rStart.x), Rez::guiX(rStart.y));
110
if (geom->poles == 2) {
111
// Degree 1 bezier = straight line...
112
path.lineTo(Rez::guiX(geom->pnts[0].x), Rez::guiX(geom->pnts[0].y));
114
else if (geom->poles == 3) {
115
path.quadTo(Rez::guiX(geom->pnts[1].x), Rez::guiX(geom->pnts[1].y),
116
Rez::guiX(geom->pnts[0].x), Rez::guiX(geom->pnts[0].y));
118
else if (geom->poles == 4) {
119
path.cubicTo(Rez::guiX(geom->pnts[2].x), Rez::guiX(geom->pnts[2].y),
120
Rez::guiX(geom->pnts[1].x), Rez::guiX(geom->pnts[1].y),
121
Rez::guiX(geom->pnts[0].x), Rez::guiX(geom->pnts[0].y));
123
else {//can only handle lines, quads, cubes
124
Base::Console().Error("Bad pole count (%d) for BezierSegment\n", geom->poles);
125
auto itBez = geom->pnts.begin() + 1;
126
for (; itBez != geom->pnts.end(); itBez++) {
127
path.lineTo(Rez::guiX((*itBez).x),
128
Rez::guiX((*itBez).y));//show something for debugging
133
// Move painter to the beginning
134
path.moveTo(Rez::guiX(geom->pnts[0].x), Rez::guiX(geom->pnts[0].y));
136
if (geom->poles == 2) {
137
// Degree 1 bezier = straight line...
138
path.lineTo(Rez::guiX(geom->pnts[1].x), Rez::guiX(geom->pnts[1].y));
140
else if (geom->poles == 3) {
141
path.quadTo(Rez::guiX(geom->pnts[1].x), Rez::guiX(geom->pnts[1].y),
142
Rez::guiX(geom->pnts[2].x), Rez::guiX(geom->pnts[2].y));
144
else if (geom->poles == 4) {
145
path.cubicTo(Rez::guiX(geom->pnts[1].x), Rez::guiX(geom->pnts[1].y),
146
Rez::guiX(geom->pnts[2].x), Rez::guiX(geom->pnts[2].y),
147
Rez::guiX(geom->pnts[3].x), Rez::guiX(geom->pnts[3].y));
149
else {//can only handle lines, quads, cubes
150
Base::Console().Error("Bad pole count (%d) for BezierSegment\n", geom->poles);
151
auto itBez = geom->pnts.begin() + 1;
152
for (; itBez != geom->pnts.end(); itBez++) {
153
path.lineTo(Rez::guiX((*itBez).x),
154
Rez::guiX((*itBez).y));//show something for debugging
159
case TechDraw::BSPLINE: {
160
TechDraw::BSplinePtr geom = std::static_pointer_cast<TechDraw::BSpline>(baseGeom);
161
if (baseGeom->getReversed()) {
162
// Move painter to the end of our last segment
163
std::vector<TechDraw::BezierSegment>::const_reverse_iterator it =
164
geom->segments.rbegin();
165
Base::Vector3d rStart = it->pnts.back();
166
path.moveTo(Rez::guiX(rStart.x), Rez::guiX(rStart.y));
168
for (; it != geom->segments.rend(); ++it) {
169
// At this point, the painter is either at the beginning
170
// of the first segment, or end of the last
171
if (it->poles == 2) {
172
// Degree 1 bezier = straight line...
173
path.lineTo(Rez::guiX(it->pnts[0].x), Rez::guiX(it->pnts[0].y));
175
else if (it->poles == 3) {
176
path.quadTo(Rez::guiX(it->pnts[1].x), Rez::guiX(it->pnts[1].y),
177
Rez::guiX(it->pnts[0].x), Rez::guiX(it->pnts[0].y));
179
else if (it->poles == 4) {
180
path.cubicTo(Rez::guiX(it->pnts[2].x), Rez::guiX(it->pnts[2].y),
181
Rez::guiX(it->pnts[1].x), Rez::guiX(it->pnts[1].y),
182
Rez::guiX(it->pnts[0].x), Rez::guiX(it->pnts[0].y));
184
else {//can only handle lines, quads, cubes
185
Base::Console().Error(
186
"Bad pole count (%d) for BezierSegment of B-spline geometry\n",
188
path.lineTo(it->pnts[1].x, it->pnts[1].y);//show something for debugging
193
// Move painter to the beginning of our first segment
194
std::vector<TechDraw::BezierSegment>::const_iterator it = geom->segments.begin();
195
path.moveTo(Rez::guiX(it->pnts[0].x), Rez::guiX(it->pnts[0].y));
197
for (; it != geom->segments.end(); ++it) {
198
// At this point, the painter is either at the beginning
199
// of the first segment, or end of the last
200
if (it->poles == 2) {
201
// Degree 1 bezier = straight line...
202
path.lineTo(Rez::guiX(it->pnts[1].x), Rez::guiX(it->pnts[1].y));
204
else if (it->poles == 3) {
205
path.quadTo(Rez::guiX(it->pnts[1].x), Rez::guiX(it->pnts[1].y),
206
Rez::guiX(it->pnts[2].x), Rez::guiX(it->pnts[2].y));
208
else if (it->poles == 4) {
209
path.cubicTo(Rez::guiX(it->pnts[1].x), Rez::guiX(it->pnts[1].y),
210
Rez::guiX(it->pnts[2].x), Rez::guiX(it->pnts[2].y),
211
Rez::guiX(it->pnts[3].x), Rez::guiX(it->pnts[3].y));
214
Base::Console().Error(
215
"Bad pole count (%d) for BezierSegment of B-spline geometry\n",
217
path.lineTo(it->pnts[1].x, it->pnts[1].y);//show something for debugging
222
case TechDraw::GENERIC: {
223
TechDraw::GenericPtr geom = std::static_pointer_cast<TechDraw::Generic>(baseGeom);
224
if (baseGeom->getReversed()) {
225
if (!geom->points.empty()) {
226
Base::Vector3d rStart = geom->points.back();
227
path.moveTo(Rez::guiX(rStart.x), Rez::guiX(rStart.y));
229
std::vector<Base::Vector3d>::const_reverse_iterator it = geom->points.rbegin();
230
for (++it; it != geom->points.rend(); ++it) {
231
path.lineTo(Rez::guiX((*it).x), Rez::guiX((*it).y));
235
path.moveTo(Rez::guiX(geom->points[0].x), Rez::guiX(geom->points[0].y));
236
std::vector<Base::Vector3d>::const_iterator it = geom->points.begin();
237
for (++it; it != geom->points.end(); ++it) {
238
path.lineTo(Rez::guiX((*it).x), Rez::guiX((*it).y));
243
Base::Console().Error("Error - geomToPainterPath - UNKNOWN geomType: %d\n",
244
static_cast<int>(baseGeom->getGeomType()));
248
//old rotate path logic. now done on App side.
252
// path = t.map(path);
259
// As called by arc of ellipse case:
260
// pathArc(path, geom->major, geom->minor, geom->angle, geom->largeArc, geom->cw,
261
// geom->endPnt.x, geom->endPnt.y,
262
// geom->startPnt.x, geom->startPnt.y);
263
void PathBuilder::pathArc(QPainterPath& path, double rx, double ry, double x_axis_rotation,
264
bool large_arc_flag, bool sweep_flag, double x, double y, double curx,
267
double sin_th, cos_th;
268
double a00, a01, a10, a11;
269
double x0, y0, x1, y1, xc, yc;
270
double d, sfactor, sfactor_sq;
271
double th0, th1, th_arc;
273
double dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check;
278
sin_th = qSin(x_axis_rotation);
279
cos_th = qCos(x_axis_rotation);
281
dx = (curx - x) / 2.0;
282
dy = (cury - y) / 2.0;
283
dx1 = cos_th * dx + sin_th * dy;
284
dy1 = -sin_th * dx + cos_th * dy;
289
/* Spec : check if radii are large enough */
290
check = Px / Pr1 + Py / Pr2;
292
rx = rx * qSqrt(check);
293
ry = ry * qSqrt(check);
300
x0 = a00 * curx + a01 * cury;
301
y0 = a10 * curx + a11 * cury;
302
x1 = a00 * x + a01 * y;
303
y1 = a10 * x + a11 * y;
304
/* (x0, y0) is current point in transformed coordinate space.
305
(x1, y1) is new point in transformed coordinate space.
307
The arc fits a unit-radius circle in this space.
309
d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
310
sfactor_sq = 1.0 / d - 0.25;
314
sfactor = qSqrt(sfactor_sq);
316
if (sweep_flag == large_arc_flag)
319
xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
320
yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
321
/* (xc, yc) is center of the circle. */
323
th0 = qAtan2(y0 - yc, x0 - xc);
324
th1 = qAtan2(y1 - yc, x1 - xc);
327
if (th_arc < 0 && sweep_flag)
329
else if (th_arc > 0 && !sweep_flag)
332
n_segs = qCeil(qAbs(th_arc / (M_PI * 0.5 + 0.001)));
334
path.moveTo(curx, cury);
336
for (i = 0; i < n_segs; i++) {
337
pathArcSegment(path, xc, yc, th0 + i * th_arc / n_segs, th0 + (i + 1) * th_arc / n_segs, rx,
338
ry, x_axis_rotation);
342
void PathBuilder::pathArcSegment(QPainterPath& path, double xc, double yc, double th0, double th1,
343
double rx, double ry, double xAxisRotation) const
346
double a00, a01, a10, a11;
347
double x1, y1, x2, y2, x3, y3;
351
sinTh = qSin(xAxisRotation);
352
cosTh = qCos(xAxisRotation);
359
thHalf = 0.5 * (th1 - th0);
360
t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf);
361
x1 = xc + qCos(th0) - t * qSin(th0);
362
y1 = yc + qSin(th0) + t * qCos(th0);
365
x2 = x3 + t * qSin(th1);
366
y2 = y3 - t * qCos(th1);
368
path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
369
a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);