ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
SinusxFilter.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "SinusxFilter.h"
9 
10 // CV_DB_LIB
11 #include <CVLog.h>
12 #include <ecvPointCloud.h>
13 #include <ecvPolyline.h>
14 
15 // Qt
16 #include <QDialog>
17 #include <QFile>
18 
19 // Qt5/Qt6 Compatibility
20 #include <QtCompat.h>
21 
22 // System
23 #include <cstring>
24 
26  : FileIOFilter({"_Sinusx Filter",
27  DEFAULT_PRIORITY, // priority
28  QStringList{"sx", "sinusx"}, "sx",
29  QStringList{"Sinusx curve (*.sx)"},
30  QStringList{"Sinusx curve (*.sx)"}, Import | Export}) {}
31 
33  bool& multiple,
34  bool& exclusive) const {
35  if (type == CV_TYPES::POLY_LINE) {
36  multiple = true;
37  exclusive = true;
38  return true;
39  }
40  return false;
41 }
42 
43 QString MakeSinusxName(QString name) {
44  // no space characters
45  name.replace(' ', '_');
46 
47  return name;
48 }
49 
51  const QString& filename,
52  const SaveParameters& parameters) {
53  if (!entity || filename.isEmpty()) return CC_FERR_BAD_ARGUMENT;
54 
55  // look for polylines only
56  std::vector<ccPolyline*> profiles;
57  try {
58  if (entity->isA(CV_TYPES::POLY_LINE)) {
59  profiles.push_back(static_cast<ccPolyline*>(entity));
60  } else if (entity->isA(CV_TYPES::HIERARCHY_OBJECT)) {
61  for (unsigned i = 0; i < entity->getChildrenNumber(); ++i)
62  if (entity->getChild(i) &&
63  entity->getChild(i)->isA(CV_TYPES::POLY_LINE))
64  profiles.push_back(
65  static_cast<ccPolyline*>(entity->getChild(i)));
66  }
67  } catch (const std::bad_alloc&) {
69  }
70 
71  if (profiles.empty()) return CC_FERR_NO_SAVE;
72 
73  // open ASCII file for writing
74  QFile file(filename);
75  if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
76  return CC_FERR_WRITING;
77 
78  QTextStream outFile(&file);
79  static const int s_precision = 12;
80  outFile.setRealNumberNotation(QTextStream::FixedNotation);
81  outFile.setRealNumberPrecision(s_precision);
82 
84 
85  // write header
86  outFile << "C Generated by CLOUDVIEWER " << QtCompat::endl;
87 
88  // for each profile
89  for (size_t i = 0; i < profiles.size(); ++i) {
90  ccPolyline* poly = profiles[i];
91  unsigned vertCount = poly ? poly->size() : 0;
92  if (vertCount < 2) {
93  // invalid size
94  CVLog::Warning(QString("[Sinusx] Polyline '%1' does not have "
95  "enough vertices")
96  .arg(poly ? poly->getName()
97  : QStringLiteral("unnamed")));
98  continue;
99  }
100 
101  bool is2D = poly->is2DMode();
102 
103  int upDir = 2;
104  if (is2D && poly->hasMetaData(ccPolyline::MetaKeyUpDir())) {
105  bool ok;
106  upDir = poly->getMetaData(ccPolyline::MetaKeyUpDir()).toInt(&ok);
107  if (!ok) upDir = 2; // restore default value
108  }
109 
110  // new block
111  outFile << "B S"
112  << QtCompat::endl; // B + curve type (S = set of 3D points)
113  outFile << "CN " << poly->getName() << QtCompat::endl; // CN + name
114  outFile << "CP 1 " << (poly->isClosed() ? 1 : 0)
115  << QtCompat::endl; // CP + isLinked + isClosed
116  outFile << "CP " << (upDir == 2 ? 0 : (upDir == 1 ? 2 : 1))
117  << QtCompat::endl; // base plane: 0 = (XY), 1 = (YZ), 2 = (ZX)
118 
119  for (unsigned j = 0; j < vertCount; ++j) {
120  const CCVector3* P = poly->getPoint(j);
121  CCVector3d Pg = poly->toGlobal3d(*P);
122 
123  for (unsigned k = 0; k < 3; ++k) {
124  outFile << " ";
125  if (P->u[k] >= 0) outFile << "+";
126  outFile << QString::number(Pg.u[k], 'E', s_precision);
127  }
128  outFile << " A" << QtCompat::endl;
129  }
130 
132  }
133 
134  file.close();
135 
136  return result;
137 }
138 
139 enum CurveType {
140  INVALID = -1,
141  CUREV_S = 0,
142  CURVE_P = 1,
143  CURVE_N = 2,
144  CURVE_C = 3
145 };
146 const QChar SHORTCUT[] = {'S', 'P', 'N', 'C'};
147 
149  ccHObject& container,
150  LoadParameters& parameters) {
151  // open file
152  QFile file(filename);
153  if (!file.open(QFile::ReadOnly)) return CC_FERR_READING;
154  QTextStream stream(&file);
155 
156  QString currentLine("C");
157  ccPolyline* currentPoly = 0;
158  ccPointCloud* currentVertices = 0;
159  unsigned lineNumber = 0;
160  CurveType curveType = INVALID;
161  unsigned cpIndex = 0;
163  CCVector3d Pshift(0, 0, 0);
164  bool firstVertex = true;
165 
166  while (!currentLine.isEmpty() && file.error() == QFile::NoError) {
167  currentLine = stream.readLine();
168  ++lineNumber;
169 
170  if (currentLine.startsWith("C ")) {
171  // ignore comments
172  continue;
173  } else if (currentLine.startsWith("B")) {
174  // new block
175  if (currentPoly) {
176  if (currentVertices && currentVertices->size() != 0 &&
177  currentVertices->resize(currentVertices->size()) &&
178  currentPoly->addPointIndex(0, currentVertices->size())) {
179  container.addChild(currentPoly);
180  } else {
181  delete currentPoly;
182  }
183  currentPoly = 0;
184  currentVertices = 0;
185  }
186  // read type
187  QStringList tokens = qtCompatSplitRegex(currentLine, "\\s+",
189  if (tokens.size() < 2 || tokens[1].length() > 1) {
190  CVLog::Warning(QString("[SinusX] Line %1 is corrupted")
191  .arg(lineNumber));
193  continue;
194  }
195  QChar curveTypeChar = tokens[1].at(0);
196  curveType = INVALID;
197  if (curveTypeChar == SHORTCUT[CUREV_S])
198  curveType = CUREV_S;
199  else if (curveTypeChar == SHORTCUT[CURVE_P])
200  curveType = CURVE_P;
201  else if (curveTypeChar == SHORTCUT[CURVE_N])
202  curveType = CURVE_N;
203  else if (curveTypeChar == SHORTCUT[CURVE_C])
204  curveType = CURVE_C;
205 
206  if (curveType == INVALID) {
207  CVLog::Warning(QString("[SinusX] Unhandled curve type '%1' on "
208  "line '%2'!")
209  .arg(curveTypeChar)
210  .arg(lineNumber));
212  continue;
213  }
214 
215  // TODO: what about the local coordinate system and scale?! (7 last
216  // values)
217  if (tokens.size() > 7) {
218  }
219 
220  // block is ready
221  currentVertices = new ccPointCloud("vertices");
222  currentPoly = new ccPolyline(currentVertices);
223  currentPoly->addChild(currentVertices);
224  currentVertices->setEnabled(false);
225  cpIndex = 0;
226  } else if (currentPoly) {
227  if (currentLine.startsWith("CN")) {
228  if (currentLine.length() > 3) {
229  QString name = currentLine.right(currentLine.length() - 3);
230  currentPoly->setName(name);
231  }
232  } else if (currentLine.startsWith("CP")) {
233  QStringList tokens = qtCompatSplitRegex(
234  currentLine, "\\s+", QtCompat::SkipEmptyParts);
235 
236  switch (cpIndex) {
237  case 0: // first 'CP' line
238  {
239  // expected: CP + 'connected' + 'closed' flags
240  bool ok = (tokens.size() == 3);
241  if (ok) {
242  bool ok1 = true, ok2 = true;
243  int isConnected = tokens[1].toInt(&ok1);
244  int isClosed = tokens[2].toInt(&ok2);
245  ok = ok1 && ok2;
246  if (ok) {
247  if (isConnected == 0) {
248  // points are not connected?!
249  //--> we simply hide the polyline and
250  // display its vertices
251  currentPoly->setVisible(false);
252  currentVertices->setEnabled(true);
253  }
254  currentPoly->setClosed(isClosed != 0);
255  }
256  }
257  if (!ok) {
259  QString("[SinusX] Line %1 is corrupted "
260  "(expected: 'CP connected_flag "
261  "closed_flag')")
262  .arg(lineNumber));
264  continue;
265  }
266  ++cpIndex;
267  } break;
268 
269  case 1: // second 'CP' line
270  {
271  if (curveType == CUREV_S) {
272  ++cpIndex;
273  // no break: we go directly to the next case
274  // (cpIndex = 2)
275  } else if (curveType == CURVE_P) {
276  // nothing particular for profiles (they are not
277  // handled in the same way in CC!)
278  ++cpIndex;
279  break;
280  } else if (curveType == CURVE_N) {
281  // expected: CP + const_altitude
282  bool ok = (tokens.size() == 2);
283  if (ok) {
284  double z = tokens[1].toDouble(&ok);
285  if (ok)
286  currentPoly->setMetaData(
288  QVariant(z));
289  }
290  if (!ok) {
291  CVLog::Warning(QString("[SinusX] Line %1 is "
292  "corrupted (expected: "
293  "'CP const_altitude')")
294  .arg(lineNumber));
296  continue;
297  }
298  ++cpIndex;
299  break;
300  } else if (curveType == CURVE_C) {
301  // skip the next 16 values
302  int skipped = tokens.size() -
303  1; // all but the 'CP' keyword
304  while (skipped < 16 && !currentLine.isEmpty() &&
305  file.error() == QFile::NoError) {
306  currentLine = stream.readLine();
307  ++lineNumber;
308  tokens = qtCompatSplitRegex(
309  currentLine, "\\s+",
311  skipped += tokens.size();
312  }
313  assert(skipped == 16); // no more than 16 normally!
314  ++cpIndex;
315  break;
316  } else {
317  assert(false);
318  ++cpIndex;
319  break;
320  }
321  }
322 
323  case 2: {
324  // CP + base plane: 0 = (XY), 1 = (YZ), 2 = (ZX)
325  bool ok = (tokens.size() == 2);
326  if (ok) {
327  int vertDir = 2;
328  QChar basePlaneChar = tokens[1].at(0);
329  if (basePlaneChar == '0')
330  vertDir = 2;
331  else if (basePlaneChar == '1')
332  vertDir = 0;
333  else if (basePlaneChar == '2')
334  vertDir = 1;
335  else
336  ok = false;
337  if (ok)
338  currentPoly->setMetaData(
340  QVariant(vertDir));
341  }
342  if (!ok) {
344  QString("[SinusX] Line %1 is corrupted "
345  "(expected: 'CP base_plane')")
346  .arg(lineNumber));
348  continue;
349  }
350  }
351  ++cpIndex;
352  break;
353 
354  default:
355  // ignored
356  break;
357  }
358  } else if (!currentLine.isEmpty()) {
359  assert(currentVertices);
360 
361  // shoud be a point!
362  QStringList tokens = qtCompatSplitRegex(
363  currentLine, "\\s+", QtCompat::SkipEmptyParts);
364  bool ok = (tokens.size() == 4);
365  if (ok) {
366  CCVector3d Pd;
367  Pd.x = tokens[0].toDouble(&ok);
368  if (ok) {
369  Pd.y = tokens[1].toDouble(&ok);
370  if (ok) {
371  Pd.z = tokens[2].toDouble(&ok);
372  if (ok) {
373  // resize vertex cloud if necessary
374  if (currentVertices->size() ==
375  currentVertices->capacity() &&
376  !currentVertices->reserve(
377  currentVertices->size() + 10)) {
378  delete currentPoly;
380  }
381  // first point: check for 'big' coordinates
382  if (firstVertex/*currentVertices->size() == 0*/)
383  {
384  firstVertex = false;
385  bool preserveCoordinateShift = true;
386  if (HandleGlobalShift(
387  Pd, Pshift,
388  preserveCoordinateShift,
389  parameters)) {
390  if (preserveCoordinateShift) {
391  if (currentPoly)
392  currentPoly->setGlobalShift(
393  Pshift);
394  else
395  currentVertices->setGlobalShift(
396  Pshift);
397  }
399  "[SinusX::loadFile] Polyline "
400  "has been recentered! "
401  "Translation: (%.2f ; %.2f ; "
402  "%.2f)",
403  Pshift.x, Pshift.y, Pshift.z);
404  }
405  }
406 
407  currentVertices->addPoint(
408  CCVector3::fromArray((Pd + Pshift).u));
409  }
410  }
411  }
412  }
413  if (!ok) {
414  CVLog::Warning(QString("[SinusX] Line %1 is corrupted "
415  "(expected: 'X Y Z Key ...')")
416  .arg(lineNumber));
418  continue;
419  }
420  }
421  }
422  }
423 
424  // don't forget the last polyline!
425  if (currentPoly) {
426  if (currentVertices && currentVertices->size() != 0 &&
427  currentVertices->resize(currentVertices->size()) &&
428  currentPoly->addPointIndex(0, currentVertices->size())) {
429  container.addChild(currentPoly);
430  }
431  }
432 
433  return result;
434 }
int64_t CV_CLASS_ENUM
Type of object type flags (64 bits)
Definition: CVTypes.h:97
std::string filename
std::string name
char type
CC_FILE_ERROR
Typical I/O filter errors.
Definition: FileIOFilter.h:20
@ CC_FERR_WRITING
Definition: FileIOFilter.h:25
@ CC_FERR_MALFORMED_FILE
Definition: FileIOFilter.h:32
@ CC_FERR_BAD_ARGUMENT
Definition: FileIOFilter.h:22
@ CC_FERR_NO_SAVE
Definition: FileIOFilter.h:27
@ CC_FERR_NO_ERROR
Definition: FileIOFilter.h:21
@ CC_FERR_READING
Definition: FileIOFilter.h:26
@ CC_FERR_NOT_ENOUGH_MEMORY
Definition: FileIOFilter.h:31
QStringList qtCompatSplitRegex(const QString &str, const QString &pattern, Qt::SplitBehavior behavior=Qt::KeepEmptyParts)
Definition: QtCompat.h:308
CurveType
@ CURVE_C
@ CURVE_N
@ CURVE_P
@ INVALID
@ CUREV_S
const QChar SHORTCUT[]
QString MakeSinusxName(QString name)
core::Tensor result
Definition: VtkUtils.cpp:76
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
Generic file I/O filter.
Definition: FileIOFilter.h:46
static constexpr float DEFAULT_PRIORITY
Definition: FileIOFilter.h:313
static bool HandleGlobalShift(const CCVector3d &P, CCVector3d &Pshift, bool &preserveCoordinateShift, LoadParameters &loadParameters, bool useInputCoordinatesShiftIfPossible=false)
Shortcut to the ecvGlobalShiftManager mechanism specific for files.
virtual bool canSave(CV_CLASS_ENUM type, bool &multiple, bool &exclusive) const override
Returns whether this I/O filter can save the specified type of entity.
virtual CC_FILE_ERROR loadFile(const QString &filename, ccHObject &container, LoadParameters &parameters) override
Loads one or more entities from a file.
virtual CC_FILE_ERROR saveToFile(ccHObject *entity, const QString &filename, const SaveParameters &parameters) override
Saves an entity (or a group of) to a file.
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
virtual void setVisible(bool state)
Sets entity visibility.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
QVariant getMetaData(const QString &key) const
Returns a given associated meta data.
bool hasMetaData(const QString &key) const
Returns whether a meta-data element with the given key exists or not.
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
Colored polyline.
Definition: ecvPolyline.h:24
virtual void setGlobalShift(const CCVector3d &shift) override
Sets shift applied to original coordinates (information storage only)
static QString MetaKeyUpDir()
Definition: ecvPolyline.h:220
bool is2DMode() const
Returns whether the polyline is considered as 2D or 3D.
Definition: ecvPolyline.h:63
static QString MetaKeyConstAltitude()
Meta data key: contour plot constant altitude (for contour plots, etc.)
Definition: ecvPolyline.h:224
CCVector3d toGlobal3d(const Vector3Tpl< T > &Plocal) const
Returns the point back-projected into the original coordinates system.
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
unsigned capacity() const
Returns cloud capacity (i.e. reserved size)
void setClosed(bool state)
Sets whether the polyline is closed or not.
Definition: Polyline.h:29
bool isClosed() const
Returns whether the polyline is closed or not.
Definition: Polyline.h:26
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
unsigned size() const override
Returns the number of points.
const CCVector3 * getPoint(unsigned index) const override
Returns the ith point.
@ HIERARCHY_OBJECT
Definition: CVTypes.h:103
@ POLY_LINE
Definition: CVTypes.h:112
constexpr Qt::SplitBehavior SkipEmptyParts
Definition: QtCompat.h:302
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
Generic loading parameters.
Definition: FileIOFilter.h:51
Generic saving parameters.
Definition: FileIOFilter.h:84