Engauge Digitizer 2
Loading...
Searching...
No Matches
CallbackAxisPointsAbstract.cpp
Go to the documentation of this file.
1/******************************************************************************************************
2 * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3 * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4 * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5 ******************************************************************************************************/
6
8#include "EngaugeAssert.h"
9#include "Logger.h"
10#include "Point.h"
11#include <qmath.h>
12#include "QtToString.h"
13#include "Transformation.h"
14
15// Epsilon test values
16const double ONE_PIXEL = 1.0; // Screen coordinates
17const double ZERO_EPSILON = 0.0; // Graph coordinates
18
20 DocumentAxesPointsRequired documentAxesPointsRequired) :
21 m_modelCoords (modelCoords),
22 m_isError (false),
23 m_documentAxesPointsRequired (documentAxesPointsRequired)
24{
25}
26
28 const QString pointIdentifierOverride,
29 const QPointF &posScreenOverride,
30 const QPointF &posGraphOverride,
31 DocumentAxesPointsRequired documentAxesPointsRequired) :
32 m_modelCoords (modelCoords),
33 m_pointIdentifierOverride (pointIdentifierOverride),
34 m_posScreenOverride (posScreenOverride),
35 m_posGraphOverride (posGraphOverride),
36 m_isError (false),
37 m_documentAxesPointsRequired (documentAxesPointsRequired)
38{
39}
40
41bool CallbackAxisPointsAbstract::anyPointsRepeatPair (const CoordPairVector &vector,
42 double epsilon) const
43{
44 for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
45 for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
46
47 if (qAbs (vector.at(pointLeft).x() - vector.at(pointRight).x()) <= epsilon &&
48 qAbs (vector.at(pointLeft).y() - vector.at(pointRight).y()) <= epsilon) {
49
50 // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
51 return true;
52 }
53 }
54 }
55
56 // No columns repeat
57 return false;
58}
59
60bool CallbackAxisPointsAbstract::anyPointsRepeatSingle (const CoordSingleVector &vector,
61 double epsilon) const
62{
63 for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
64 for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
65
66 if (qAbs (vector.at(pointLeft) - vector.at(pointRight)) <= epsilon) {
67
68 // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
69 return true;
70 }
71 }
72 }
73
74 // No columns repeat
75 return false;
76}
77
79 const Point &point)
80{
81 QPointF posScreen = point.posScreen ();
82 QPointF posGraph = point.posGraph ();
83
84 if (m_pointIdentifierOverride == point.identifier ()) {
85
86 // Override the old point coordinates with its new (if all tests are passed) coordinates
87 posScreen = m_posScreenOverride;
88 posGraph = m_posGraphOverride;
89 }
90
91 // Try to compute transform
92 if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2) {
93 return callbackRequire2AxisPoints (posScreen,
94 posGraph);
95 } else if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
96 return callbackRequire3AxisPoints (posScreen,
97 posGraph);
98 } else {
99 return callbackRequire4AxisPoints (point.isXOnly(),
100 posScreen,
101 posGraph);
102 }
103}
104
105CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire2AxisPoints (const QPointF &posScreen,
106 const QPointF &posGraph)
107{
109
110 // Update range variables. The same nonzero length value is stored in every x and y coordinate of every axis point
111 m_xGraphLow = 0;
112 m_yGraphLow = 0;
113 m_xGraphHigh = posGraph.x();
114 m_yGraphHigh = posGraph.x();
115
116 int numberPoints = m_screenInputs.count();
117 if (numberPoints < 2) {
118
119 // Append new point
120 m_screenInputs.push_back (posScreen);
121 m_graphOutputs.push_back (posGraph);
122 numberPoints = m_screenInputs.count();
123
124 if (numberPoints == 2) {
125 loadTransforms2 ();
126 }
127
128 // Error checking
129 if (anyPointsRepeatPair (m_screenInputs,
130 ONE_PIXEL)) {
131
132 m_isError = true;
133 m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
135
136 }
137 }
138
139 if (m_screenInputs.count() > 1) {
140
141 // There are enough axis points so quit
143
144 }
145
146 return rtn;
147}
148
149CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire3AxisPoints (const QPointF &posScreen,
150 const QPointF &posGraph)
151{
153
154 // Update range variables
155 int numberPoints = m_screenInputs.count();
156 if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
157 if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
158 if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
159 if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
160
161 if (numberPoints < 3) {
162
163 // Append new point
164 m_screenInputs.push_back (posScreen);
165 m_graphOutputs.push_back (posGraph);
166 numberPoints = m_screenInputs.count();
167
168 if (numberPoints == 3) {
169 loadTransforms3 ();
170 }
171
172 // Error checking
173 if ((m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2 ||
174 m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) &&
175 anyPointsRepeatPair (m_screenInputs, ONE_PIXEL)) {
176
177 m_isError = true;
178 m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
180
181 } else if ((m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2 ||
182 m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) &&
183 anyPointsRepeatPair (m_graphOutputs, ZERO_EPSILON)) {
184
185 m_isError = true;
186 m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
188
189 } else if ((numberPoints == 3) && threePointsAreCollinear (m_screenInputsTransform,
190 COORD_IS_LINEAR,
191 COORD_IS_LINEAR)) {
192
193 m_isError = true;
194 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
196
197 } else if ((numberPoints == 3) && threePointsAreCollinear (m_graphOutputsTransform,
198 logXGraph (),
199 logYGraph ())) {
200
201 m_isError = true;
202 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
204
205 }
206 }
207
208 if (m_screenInputs.count() > 2) {
209
210 // There are enough axis points so quit
212
213 }
214
215 return rtn;
216}
217
218CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire4AxisPoints (bool isXOnly,
219 const QPointF &posScreen,
220 const QPointF &posGraph)
221{
223
224 // Update range variables
225 int numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
226 if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
227 if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
228 if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
229 if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
230
231 if (numberPoints < 4) {
232
233 // Append the new point
234 if (isXOnly) {
235
236 m_screenInputsX.push_back (posScreen);
237 m_graphOutputsX.push_back (posGraph.x());
238
239 } else {
240
241 m_screenInputsY.push_back (posScreen);
242 m_graphOutputsY.push_back (posGraph.y());
243
244 }
245
246 numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
247 if (numberPoints == 4) {
248 loadTransforms4 ();
249 }
250 }
251
252 if (m_screenInputsX.count() > 2) {
253
254 m_isError = true;
255 m_errorMessage = QObject::tr ("Too many x axis points. There should only be two");
257
258 } else if (m_screenInputsY.count() > 2) {
259
260 m_isError = true;
261 m_errorMessage = QObject::tr ("Too many y axis points. There should only be two");
263
264 } else {
265
266 if ((m_screenInputsX.count() == 2) &&
267 (m_screenInputsY.count() == 2)) {
268
269 // Done, although an error may intrude
271 }
272
273 // Error checking
274 if (anyPointsRepeatPair (m_screenInputsX,
275 ONE_PIXEL) ||
276 anyPointsRepeatPair (m_screenInputsY,
277 ONE_PIXEL)) {
278
279 m_isError = true;
280 m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
282
283 } else if (anyPointsRepeatSingle (m_graphOutputsX,
284 ZERO_EPSILON) ||
285 anyPointsRepeatSingle (m_graphOutputsY,
286 ZERO_EPSILON)) {
287
288 m_isError = true;
289 m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
291
292 } else if ((numberPoints == 4) && threePointsAreCollinear (m_screenInputsTransform,
293 COORD_IS_LINEAR,
294 COORD_IS_LINEAR)) {
295
296 m_isError = true;
297 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
299
300 } else if ((numberPoints == 4) && threePointsAreCollinear (m_graphOutputsTransform,
301 logXGraph (),
302 logYGraph ())) {
303
304 m_isError = true;
305 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
307
308 }
309 }
310
311 return rtn;
312}
313
315{
316 return m_documentAxesPointsRequired;
317}
318
319void CallbackAxisPointsAbstract::loadTransforms2 ()
320{
321 // To get a third point from two existing points we compute the vector between the first 2 points and then take
322 // the cross product with the out-of-plane unit vector to get the perpendicular vector, and the endpoint of that
323 // is used as the third point. This implicitly assumes that the graph-to-screen coordinates scaling is the
324 // same in both directions. The advantage of this approach is that no assumptions are made about the inputs
325
326 double d0To1ScreenX = m_screenInputs.at (1).x () - m_screenInputs.at (0).x ();
327 double d0To1ScreenY = m_screenInputs.at (1).y () - m_screenInputs.at (0).y ();
328 double d0To1ScreenZ = 0;
329 double d0To1GraphX = m_graphOutputs.at (1).x () - m_graphOutputs.at (0).x ();
330 double d0To1GraphY = m_graphOutputs.at (1).y () - m_graphOutputs.at (0).y ();
331 double d0To1GraphZ = 0;
332
333 double unitNormalX = 0;
334 double unitNormalY = 0;
335 double unitNormalZ = 1;
336
337 double d0To2ScreenX = unitNormalY * d0To1ScreenZ - unitNormalZ * d0To1ScreenY;
338 double d0To2ScreenY = unitNormalZ * d0To1ScreenX - unitNormalX * d0To1ScreenZ;
339 double d0To2GraphX = unitNormalY * d0To1GraphZ - unitNormalZ * d0To1GraphY;
340 double d0To2GraphY = unitNormalZ * d0To1GraphX - unitNormalX * d0To1GraphZ;
341
342 // Hack since +Y for screen coordinates is down but up for graph coordinates. Users expect +Y to be up
343 // so we rotate screen delta by 180 degrees
344 const double FLIP_Y_SCREEN = -1.0;
345
346 double screenX2 = m_screenInputs.at (0).x () + FLIP_Y_SCREEN * d0To2ScreenX;
347 double screenY2 = m_screenInputs.at (0).y () + FLIP_Y_SCREEN * d0To2ScreenY;
348 double graphX2 = m_graphOutputs.at (0).x () + d0To2GraphX;
349 double graphY2 = m_graphOutputs.at (0).y () + d0To2GraphY;
350
351 // Screen coordinates
352 m_screenInputsTransform = QTransform (m_screenInputs.at(0).x(), m_screenInputs.at(1).x(), screenX2,
353 m_screenInputs.at(0).y(), m_screenInputs.at(1).y(), screenY2,
354 1.0 , 1.0 , 1.0 );
355
356 // Graph coordinates
357 m_graphOutputsTransform = QTransform (m_graphOutputs.at(0).x(), m_graphOutputs.at(1).x(), graphX2,
358 m_graphOutputs.at(0).y(), m_graphOutputs.at(1).y(), graphY2,
359 1.0 , 1.0 , 1.0 );
360}
361
362void CallbackAxisPointsAbstract::loadTransforms3 ()
363{
364 // Screen coordinates
365 m_screenInputsTransform = QTransform (m_screenInputs.at(0).x(), m_screenInputs.at(1).x(), m_screenInputs.at(2).x(),
366 m_screenInputs.at(0).y(), m_screenInputs.at(1).y(), m_screenInputs.at(2).y(),
367 1.0 , 1.0 , 1.0 );
368
369 // Graph coordinates
370 m_graphOutputsTransform = QTransform (m_graphOutputs.at(0).x(), m_graphOutputs.at(1).x(), m_graphOutputs.at(2).x(),
371 m_graphOutputs.at(0).y(), m_graphOutputs.at(1).y(), m_graphOutputs.at(2).y(),
372 1.0 , 1.0 , 1.0 );
373}
374
375void CallbackAxisPointsAbstract::loadTransforms4 ()
376{
377 double x1Screen = m_screenInputsX.at(0).x();
378 double y1Screen = m_screenInputsX.at(0).y();
379 double x2Screen = m_screenInputsX.at(1).x();
380 double y2Screen = m_screenInputsX.at(1).y();
381 double x3Screen = m_screenInputsY.at(0).x();
382 double y3Screen = m_screenInputsY.at(0).y();
383 double x4Screen = m_screenInputsY.at(1).x();
384 double y4Screen = m_screenInputsY.at(1).y();
385
386 // Each of the four axes points has only one coordinate
387 double x1Graph = m_graphOutputsX.at(0);
388 double x2Graph = m_graphOutputsX.at(1);
389 double y3Graph = m_graphOutputsY.at(0);
390 double y4Graph = m_graphOutputsY.at(1);
391
392 // Intersect the two lines of the two axes. The lines are defined parametrically for the screen coordinates, with
393 // points 1 and 2 on the x axis and points 3 and 4 on the y axis, as:
394 // x = (1 - sx) * x1 + sx * x2
395 // y = (1 - sx) * y1 + sx * y2
396 // x = (1 - sy) * x3 + sy * x4
397 // y = (1 - sy) * y3 + sy * y4
398 // Intersection of the 2 lines is at (x,y). Solving for sx and sy using Cramer's rule where Ax=b
399 // (x1 - x3) (x1 - x2 x4 - x3) (sx)
400 // (y1 - y3) = (y1 - y2 y4 - y3) (sy)
401 double A00 = x1Screen - x2Screen;
402 double A01 = x4Screen - x3Screen;
403 double A10 = y1Screen - y2Screen;
404 double A11 = y4Screen - y3Screen;
405 double b0 = x1Screen - x3Screen;
406 double b1 = y1Screen - y3Screen;
407 double numeratorx = (b0 * A11 - A01 * b1);
408 double numeratory = (A00 * b1 - b0 * A10);
409 double denominator = (A00 * A11 - A01 * A10);
410 double sx = numeratorx / denominator;
411 double sy = numeratory / denominator;
412
413 // Intersection point. For the graph coordinates, the initial implementation assumes cartesian coordinates
414 double xIntScreen = (1.0 - sx) * x1Screen + sx * x2Screen;
415 double yIntScreen = (1.0 - sy) * y3Screen + sy * y4Screen;
416 double xIntGraph, yIntGraph;
417 if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LINEAR) {
418 xIntGraph = (1.0 - sx) * x1Graph + sx * x2Graph;
419 } else {
420 xIntGraph = qExp ((1.0 - sx) * qLn (x1Graph) + sx * qLn (x2Graph));
421 }
422 if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR) {
423 yIntGraph = (1.0 - sy) * y3Graph + sy * y4Graph;
424 } else {
425 yIntGraph = qExp ((1.0 - sy) * qLn (y3Graph) + sy * qLn (y4Graph));
426 }
427
428 // Distances of 4 axis points from interception
429 double distance1 = qSqrt ((x1Screen - xIntScreen) * (x1Screen - xIntScreen) +
430 (y1Screen - yIntScreen) * (y1Screen - yIntScreen));
431 double distance2 = qSqrt ((x2Screen - xIntScreen) * (x2Screen - xIntScreen) +
432 (y2Screen - yIntScreen) * (y2Screen - yIntScreen));
433 double distance3 = qSqrt ((x3Screen - xIntScreen) * (x3Screen - xIntScreen) +
434 (y3Screen - yIntScreen) * (y3Screen - yIntScreen));
435 double distance4 = qSqrt ((x4Screen - xIntScreen) * (x4Screen - xIntScreen) +
436 (y4Screen - yIntScreen) * (y4Screen - yIntScreen));
437
438 // We now have too many data points with both x and y coordinates:
439 // (xInt,yInt) (xInt,y3) (xInt,y4) (x1,yInt) (x2,yInt)
440 // so we pick just 3, making sure that those 3 are widely separated
441 // (xInt,yInt) (x axis point furthest from xInt,yInt) (y axis point furthest from xInt,yInt)
442 double xFurthestXAxisScreen, yFurthestXAxisScreen, xFurthestYAxisScreen, yFurthestYAxisScreen;
443 double xFurthestXAxisGraph, yFurthestXAxisGraph, xFurthestYAxisGraph, yFurthestYAxisGraph;
444 if (distance1 < distance2) {
445 xFurthestXAxisScreen = x2Screen;
446 yFurthestXAxisScreen = y2Screen;
447 xFurthestXAxisGraph = x2Graph;
448 yFurthestXAxisGraph = yIntGraph;
449 } else {
450 xFurthestXAxisScreen = x1Screen;
451 yFurthestXAxisScreen = y1Screen;
452 xFurthestXAxisGraph = x1Graph;
453 yFurthestXAxisGraph = yIntGraph;
454 }
455 if (distance3 < distance4) {
456 xFurthestYAxisScreen = x4Screen;
457 yFurthestYAxisScreen = y4Screen;
458 xFurthestYAxisGraph = xIntGraph;
459 yFurthestYAxisGraph = y4Graph;
460 } else {
461 xFurthestYAxisScreen = x3Screen;
462 yFurthestYAxisScreen = y3Screen;
463 xFurthestYAxisGraph = xIntGraph;
464 yFurthestYAxisGraph = y3Graph;
465 }
466
467 // Screen coordinates
468 m_screenInputsTransform = QTransform (xIntScreen, xFurthestXAxisScreen, xFurthestYAxisScreen,
469 yIntScreen, yFurthestXAxisScreen, yFurthestYAxisScreen,
470 1.0 , 1.0 , 1.0 );
471
472 // Graph coordinates
473 m_graphOutputsTransform = QTransform (xIntGraph, xFurthestXAxisGraph, xFurthestYAxisGraph,
474 yIntGraph, yFurthestXAxisGraph, yFurthestYAxisGraph,
475 1.0 , 1.0 , 1.0 );
476}
477
478CallbackAxisPointsAbstract::LinearOrLog CallbackAxisPointsAbstract::logXGraph () const
479{
480 return m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG ? COORD_IS_LOG : COORD_IS_LINEAR;
481}
482
483CallbackAxisPointsAbstract::LinearOrLog CallbackAxisPointsAbstract::logYGraph () const
484{
485 return m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG ? COORD_IS_LOG : COORD_IS_LINEAR;
486}
487
489{
490 return m_graphOutputsTransform;
491}
492
494{
495 return m_screenInputsTransform;
496}
497
499{
500 if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2) {
501 return unsigned (m_screenInputs.count());
502 } else if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
503 return unsigned (m_screenInputs.count());
504 } else {
505 return unsigned (m_screenInputsX.count() + m_screenInputsY.count());
506 }
507}
508
509bool CallbackAxisPointsAbstract::threePointsAreCollinear (const QTransform &transformIn,
510 LinearOrLog logX,
511 LinearOrLog logY) const
512{
513 double m11 = (logX == COORD_IS_LOG) ? qLn (transformIn.m11()) : transformIn.m11();
514 double m12 = (logX == COORD_IS_LOG) ? qLn (transformIn.m12()) : transformIn.m12();
515 double m13 = (logX == COORD_IS_LOG) ? qLn (transformIn.m13()) : transformIn.m13();
516 double m21 = (logY == COORD_IS_LOG) ? qLn (transformIn.m21()) : transformIn.m21();
517 double m22 = (logY == COORD_IS_LOG) ? qLn (transformIn.m22()) : transformIn.m22();
518 double m23 = (logY == COORD_IS_LOG) ? qLn (transformIn.m23()) : transformIn.m23();
519 double m31 = transformIn.m31();
520 double m32 = transformIn.m32();
521 double m33 = transformIn.m33();
522
523 QTransform transform (m11, m12, m13,
524 m21, m22, m23,
525 m31, m32, m33);
526
527 return !transform.isInvertible ();
528}
const double ONE_PIXEL
const double ZERO_EPSILON
QList< QPointF > CoordPairVector
QList< double > CoordSingleVector
CallbackSearchReturn
Return values for search callback methods.
@ CALLBACK_SEARCH_RETURN_CONTINUE
Continue normal execution of the search.
@ CALLBACK_SEARCH_RETURN_INTERRUPT
Immediately terminate the current search.
@ COORD_SCALE_LINEAR
Definition CoordScale.h:13
@ COORD_SCALE_LOG
Definition CoordScale.h:14
@ DOCUMENT_AXES_POINTS_REQUIRED_3
@ DOCUMENT_AXES_POINTS_REQUIRED_2
DocumentAxesPointsRequired documentAxesPointsRequired() const
Number of axes points required for the transformation.
QTransform matrixGraph() const
Returns graph coordinates matrix after transformIsDefined has already indicated success.
CallbackSearchReturn callback(const QString &curveName, const Point &point)
Callback method.
CallbackAxisPointsAbstract(const DocumentModelCoords &modelCoords, DocumentAxesPointsRequired documentAxesPointsRequired)
Constructor for when all of the existing axis points are to be processed as is.
unsigned int numberAxisPoints() const
Number of axis points which is less than 3 if the axes curve is incomplete.
QTransform matrixScreen() const
Returns screen coordinates matrix after transformIsDefined has already indicated success.
Model for DlgSettingsCoords and CmdSettingsCoords.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
CoordScale coordScaleXTheta() const
Get method for linear/log scale on x/theta.
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition Point.h:26
QPointF posGraph(ApplyHasCheck applyHasCheck=KEEP_HAS_CHECK) const
Accessor for graph position. Skip check if copying one instance to another.
Definition Point.cpp:395
QPointF posScreen() const
Accessor for screen position.
Definition Point.cpp:404
QString identifier() const
Unique identifier for a specific Point.
Definition Point.cpp:268
bool isXOnly() const
In DOCUMENT_AXES_POINTS_REQUIRED_4 modes, this is true/false if y/x coordinate is undefined.
Definition Point.cpp:286