Engauge Digitizer  2
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
16 const double ONE_PIXEL = 1.0; // Screen coordinates
17 const 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 
41 bool 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 
60 bool 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 
105 CallbackSearchReturn 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 
149 CallbackSearchReturn 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 
218 CallbackSearchReturn 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 
319 void 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 
362 void 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 
375 void 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 
478 CallbackAxisPointsAbstract::LinearOrLog CallbackAxisPointsAbstract::logXGraph () const
479 {
480  return m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG ? COORD_IS_LOG : COORD_IS_LINEAR;
481 }
482 
483 CallbackAxisPointsAbstract::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 
509 bool 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 }
bool isXOnly() const
In DOCUMENT_AXES_POINTS_REQUIRED_4 modes, this is true/false if y/x coordinate is undefined...
Definition: Point.cpp:286
QTransform matrixScreen() const
Returns screen coordinates matrix after transformIsDefined has already indicated success.
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition: Point.h:25
QPointF posScreen() const
Accessor for screen position.
Definition: Point.cpp:404
QList< QPointF > CoordPairVector
const double ONE_PIXEL
CallbackSearchReturn
Return values for search callback methods.
Continue normal execution of the search.
unsigned int numberAxisPoints() const
Number of axis points which is less than 3 if the axes curve is incomplete.
QString identifier() const
Unique identifier for a specific Point.
Definition: Point.cpp:268
QList< double > CoordSingleVector
CoordScale coordScaleXTheta() const
Get method for linear/log scale on x/theta.
QPointF posGraph(ApplyHasCheck applyHasCheck=KEEP_HAS_CHECK) const
Accessor for graph position. Skip check if copying one instance to another.
Definition: Point.cpp:395
Model for DlgSettingsCoords and CmdSettingsCoords.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
CallbackAxisPointsAbstract(const DocumentModelCoords &modelCoords, DocumentAxesPointsRequired documentAxesPointsRequired)
Constructor for when all of the existing axis points are to be processed as is.
Immediately terminate the current search.
QTransform matrixGraph() const
Returns graph coordinates matrix after transformIsDefined has already indicated success.
const double ZERO_EPSILON
DocumentAxesPointsRequired
DocumentAxesPointsRequired documentAxesPointsRequired() const
Number of axes points required for the transformation.
CallbackSearchReturn callback(const QString &curveName, const Point &point)
Callback method.