33 #include <vtkCellArray.h>
34 #include <vtkCellData.h>
35 #include <vtkDataArray.h>
36 #include <vtkExtractSelection.h>
37 #include <vtkFieldData.h>
38 #include <vtkFloatArray.h>
39 #include <vtkGeometryFilter.h>
40 #include <vtkIdTypeArray.h>
41 #include <vtkPointData.h>
42 #include <vtkPoints.h>
43 #include <vtkPolyData.h>
44 #include <vtkSelection.h>
45 #include <vtkSelectionNode.h>
46 #include <vtkSmartPointer.h>
47 #include <vtkTriangle.h>
48 #include <vtkUnsignedCharArray.h>
49 #include <vtkUnstructuredGrid.h>
53 #include <QFileDialog>
64 CVLog::Error(
"[cvSelectionExporter] polyData is nullptr");
69 CVLog::Error(
"[cvSelectionExporter] Selection is empty");
75 "[cvSelectionExporter] Selection must be CELLS for mesh "
84 CVLog::Error(
"[cvSelectionExporter] Failed to extract selection");
90 options.
name.isEmpty()
91 ? QString(
"Selection_%1_cells").arg(selectionData.
count())
93 ccMesh* mesh = vtkPolyDataToCCMesh(extracted, meshName);
99 CVLog::Print(QString(
"[cvSelectionExporter] Created mesh '%1' with %2 "
108 CVLog::Warning(QString(
"[cvSelectionExporter] Failed to save mesh "
118 vtkPolyData* polyData,
122 CVLog::Error(
"[cvSelectionExporter] polyData is nullptr");
127 CVLog::Error(
"[cvSelectionExporter] Selection is empty");
133 "[cvSelectionExporter] Selection must be POINTS for point "
142 CVLog::Error(
"[cvSelectionExporter] Failed to extract selection");
148 options.
name.isEmpty()
149 ? QString(
"Selection_%1_points").arg(selectionData.
count())
151 ccPointCloud* cloud = vtkPolyDataToCCPointCloud(extracted, cloudName);
160 QString(
"[cvSelectionExporter] Created point cloud '%1' "
163 .arg(cloud->
size()));
169 CVLog::Warning(QString(
"[cvSelectionExporter] Failed to save point "
183 CVLog::Error(
"[cvSelectionExporter] sourceCloud is nullptr");
188 CVLog::Error(
"[cvSelectionExporter] Selection is empty");
194 "[cvSelectionExporter] Selection must be POINTS for point "
200 const QVector<vtkIdType>& selectedIds = selectionData.
ids();
201 if (selectedIds.isEmpty()) {
202 CVLog::Error(
"[cvSelectionExporter] No point IDs in selection");
209 if (!refCloud.
reserve(
static_cast<unsigned>(selectedIds.size()))) {
211 "[cvSelectionExporter] Failed to reserve memory for "
217 int invalidCount = 0;
218 for (vtkIdType
id : selectedIds) {
219 if (
id >= 0 &&
static_cast<unsigned int>(
id) < cloudSize) {
227 if (invalidCount > 0) {
229 QString(
"[cvSelectionExporter] Filtered %1 invalid point IDs "
230 "(out of range [0, %2))")
235 if (validCount == 0) {
237 "[cvSelectionExporter] No valid point IDs after filtering");
246 CVLog::Error(
"[cvSelectionExporter] partialClone failed");
253 "[cvSelectionExporter] partialClone: out of memory for "
258 "[cvSelectionExporter] partialClone: out of memory for "
263 "[cvSelectionExporter] partialClone: out of memory for "
268 "[cvSelectionExporter] partialClone: out of memory for "
269 "full waveform data");
273 QString cloudName = options.
name.isEmpty()
274 ? QString(
"Selection_%1_points").arg(validCount)
276 result->setName(cloudName);
279 QString(
"[cvSelectionExporter] SUCCESS: Created point cloud "
280 "'%1' with %2 points, %3 scalar fields, "
281 "hasColors=%4, hasNormals=%5")
284 .arg(
result->getNumberOfScalarFields())
285 .arg(
result->hasColors() ?
"yes" :
"no")
286 .arg(
result->hasNormals() ?
"yes" :
"no"));
292 CVLog::Warning(QString(
"[cvSelectionExporter] Failed to save point "
307 "[cvSelectionExporter::exportFromSourceMesh] START - Direct "
308 "extraction from source ccMesh using partialClone");
311 CVLog::Error(
"[cvSelectionExporter] sourceMesh is nullptr");
316 CVLog::Error(
"[cvSelectionExporter] Selection is empty");
322 "[cvSelectionExporter] Selection must be CELLS for mesh "
328 const QVector<vtkIdType>& selectedIds = selectionData.
ids();
329 if (selectedIds.isEmpty()) {
330 CVLog::Error(
"[cvSelectionExporter] No triangle IDs in selection");
335 unsigned int meshSize = sourceMesh->
size();
336 std::vector<unsigned> triangleIndices;
337 triangleIndices.reserve(selectedIds.size());
339 for (vtkIdType
id : selectedIds) {
340 if (
id >= 0 &&
static_cast<unsigned int>(
id) < meshSize) {
341 triangleIndices.push_back(
static_cast<unsigned>(
id));
345 if (triangleIndices.empty()) {
347 "[cvSelectionExporter] No valid triangle IDs after filtering");
357 CVLog::Warning(QString(
"[cvSelectionExporter] partialClone completed "
363 CVLog::Error(
"[cvSelectionExporter] partialClone failed");
369 options.
name.isEmpty()
370 ? QString(
"Selection_%1_triangles").arg(
result->size())
372 result->setName(meshName);
381 QString(
"[cvSelectionExporter] SUCCESS: Created mesh '%1' "
382 "with %2 triangles, %3 vertices")
385 .arg(
result->getAssociatedCloud()
386 ?
result->getAssociatedCloud()->size()
393 CVLog::Warning(QString(
"[cvSelectionExporter] Failed to save mesh "
409 CVLog::Error(
"[cvSelectionExporter] Invalid parameters");
417 CVLog::Error(
"[cvSelectionExporter] Failed to extract selection");
426 object = vtkPolyDataToCCMesh(extracted, fileInfo.baseName());
428 object = vtkPolyDataToCCPointCloud(extracted, fileInfo.baseName());
434 CVLog::Error(
"[cvSelectionExporter] Failed to convert selection");
443 CVLog::Print(QString(
"[cvSelectionExporter] Exported selection to: %1")
446 CVLog::Error(QString(
"[cvSelectionExporter] Failed to export to: %1")
456 if (!polyData || selectionData.
isEmpty()) {
457 CVLog::Error(
"[cvSelectionExporter] extractSelection: Invalid input");
463 if (!vtkArray || vtkArray->GetNumberOfTuples() == 0) {
465 "[cvSelectionExporter] extractSelection: Selection array is "
470 bool isPointSelection =
474 vtkIdType maxValidId = isPointSelection ? polyData->GetNumberOfPoints()
475 : polyData->GetNumberOfCells();
480 for (vtkIdType i = 0; i < vtkArray->GetNumberOfTuples(); ++i) {
481 vtkIdType
id = vtkArray->GetValue(i);
482 if (
id >= 0 &&
id < maxValidId) {
483 validArray->InsertNextValue(
id);
487 if (validArray->GetNumberOfTuples() == 0) {
489 QString(
"[cvSelectionExporter] extractSelection: No valid IDs "
490 "(all %1 IDs are out of range [0, %2))")
491 .arg(vtkArray->GetNumberOfTuples())
496 if (validArray->GetNumberOfTuples() < vtkArray->GetNumberOfTuples()) {
498 QString(
"[cvSelectionExporter] extractSelection: Filtered "
499 "%1 invalid IDs (kept %2 of %3)")
500 .arg(vtkArray->GetNumberOfTuples() -
501 validArray->GetNumberOfTuples())
502 .arg(validArray->GetNumberOfTuples())
503 .arg(vtkArray->GetNumberOfTuples()));
510 if (isPointSelection) {
511 vtkIdType numSelectedPoints = validArray->GetNumberOfTuples();
515 newPoints->SetNumberOfPoints(numSelectedPoints);
522 vtkPointData* srcPointData = polyData->GetPointData();
523 vtkPointData* dstPointData =
result->GetPointData();
528 QString(
"[cvSelectionExporter] Source has %1 arrays, "
529 "normals=%2, scalars=%3, tcoords=%4")
530 .arg(srcPointData->GetNumberOfArrays())
531 .arg(srcPointData->GetNormals() ?
"yes" :
"no")
532 .arg(srcPointData->GetScalars() ?
"yes" :
"no")
533 .arg(srcPointData->GetTCoords() ?
"yes" :
"no"));
536 for (
int a = 0;
a < srcPointData->GetNumberOfArrays(); ++
a) {
537 vtkDataArray* arr = srcPointData->GetArray(
a);
540 QString(
"[cvSelectionExporter] Array[%1]: "
541 "name='%2', components=%3, tuples=%4, "
544 .arg(arr->GetName() ? arr->GetName()
546 .arg(arr->GetNumberOfComponents())
547 .arg(arr->GetNumberOfTuples())
548 .arg(arr->GetClassName()));
555 for (vtkIdType i = 0; i < numSelectedPoints; ++i) {
556 vtkIdType srcId = validArray->GetValue(i);
558 polyData->GetPoint(srcId, pt);
559 newPoints->SetPoint(i, pt);
562 vertices->InsertNextCell(1);
563 vertices->InsertCellPoint(i);
567 result->SetPoints(newPoints);
568 result->SetVerts(vertices);
571 if (srcPointData && numSelectedPoints > 0) {
573 for (
int a = 0;
a < srcPointData->GetNumberOfArrays(); ++
a) {
574 vtkDataArray* srcArray = srcPointData->GetArray(
a);
575 if (!srcArray)
continue;
578 const char* arrName = srcArray->GetName();
579 if (!arrName || strlen(arrName) == 0) {
581 QString(
"[cvSelectionExporter] Skipping "
588 dstArray.TakeReference(srcArray->NewInstance());
589 dstArray->SetName(arrName);
590 dstArray->SetNumberOfComponents(
591 srcArray->GetNumberOfComponents());
592 dstArray->SetNumberOfTuples(numSelectedPoints);
594 int numComp = srcArray->GetNumberOfComponents();
597 if (numSelectedPoints > 0 && numComp == 1) {
598 vtkIdType firstSrcId = validArray->GetValue(0);
599 double firstVal = srcArray->GetTuple1(firstSrcId);
601 "'%1': srcId[0]=%2 -> value=%3")
608 for (vtkIdType i = 0; i < numSelectedPoints; ++i) {
609 vtkIdType srcId = validArray->GetValue(i);
612 if (srcId < 0 || srcId >= srcArray->GetNumberOfTuples()) {
614 QString(
"[cvSelectionExporter] Invalid srcId "
615 "%1 for array '%2' (max=%3)")
618 .arg(srcArray->GetNumberOfTuples()));
623 double* tuple = srcArray->GetTuple(srcId);
624 dstArray->SetTuple(i, tuple);
628 if (numSelectedPoints > 0 && numComp == 1) {
629 double copiedVal = dstArray->GetTuple1(0);
631 "'%1': copied[0] = %2")
636 dstPointData->AddArray(dstArray);
644 vtkDataArray* srcNormals = srcPointData->GetNormals();
645 if (srcNormals && srcNormals->GetName()) {
646 vtkDataArray* dstNormals =
647 dstPointData->GetArray(srcNormals->GetName());
649 dstPointData->SetNormals(dstNormals);
654 vtkDataArray* srcScalars = srcPointData->GetScalars();
655 if (srcScalars && srcScalars->GetName()) {
656 vtkDataArray* dstScalars =
657 dstPointData->GetArray(srcScalars->GetName());
659 dstPointData->SetScalars(dstScalars);
664 vtkDataArray* srcTCoords = srcPointData->GetTCoords();
665 if (srcTCoords && srcTCoords->GetName()) {
666 vtkDataArray* dstTCoords =
667 dstPointData->GetArray(srcTCoords->GetName());
669 dstPointData->SetTCoords(dstTCoords);
677 selectionNode->SetContentType(vtkSelectionNode::INDICES);
678 selectionNode->SetFieldType(vtkSelectionNode::CELL);
679 selectionNode->SetSelectionList(validArray);
683 selection->AddNode(selectionNode);
687 extractor->SetInputData(0, polyData);
688 extractor->SetInputData(1, selection);
691 vtkUnstructuredGrid* extracted =
692 vtkUnstructuredGrid::SafeDownCast(extractor->GetOutput());
694 if (!extracted || extracted->GetNumberOfPoints() == 0) {
695 CVLog::Error(
"[cvSelectionExporter] Cell extraction failed");
700 QString(
"[cvSelectionExporter] Extracted %1 points, "
701 "%2 cells (cell selection)")
702 .arg(extracted->GetNumberOfPoints())
703 .arg(extracted->GetNumberOfCells()));
708 geometryFilter->SetInputData(extracted);
709 geometryFilter->Update();
711 vtkPolyData* filteredOutput = geometryFilter->GetOutput();
712 if (!filteredOutput || filteredOutput->GetNumberOfPoints() == 0) {
714 "[cvSelectionExporter] Geometry filter produced no output");
718 result->DeepCopy(filteredOutput);
721 if (
result->GetNumberOfPoints() == 0) {
722 CVLog::Warning(
"[cvSelectionExporter] Extraction produced 0 points");
727 vtkFieldData* srcFieldData = polyData->GetFieldData();
728 if (srcFieldData && srcFieldData->GetNumberOfArrays() > 0) {
729 vtkFieldData* dstFieldData =
result->GetFieldData();
730 for (
int i = 0; i < srcFieldData->GetNumberOfArrays(); ++i) {
731 vtkAbstractArray* arr = srcFieldData->GetAbstractArray(i);
733 dstFieldData->AddArray(arr);
737 QString(
"[cvSelectionExporter] Copied %1 field data arrays")
738 .arg(srcFieldData->GetNumberOfArrays()));
742 result->Register(
nullptr);
747 ccMesh* cvSelectionExporter::vtkPolyDataToCCMesh(vtkPolyData* polyData,
748 const QString&
name) {
762 ccPointCloud* cvSelectionExporter::vtkPolyDataToCCPointCloud(
763 vtkPolyData* polyData,
const QString&
name) {
777 bool cvSelectionExporter::saveObjectToFile(
ccHObject*
object,
781 if (!
object ||
filename.isEmpty()) {
783 "[cvSelectionExporter] Invalid parameters for file export");
789 std::string filenameStr =
filename.toStdString();
800 QString(
"[cvSelectionExporter] Successfully saved to: %1")
811 QString ext = fileInfo.suffix().toLower();
818 CVLog::Error(QString(
"[cvSelectionExporter] No filter found for "
832 CVLog::Print(QString(
"[cvSelectionExporter] Successfully saved to: %1")
836 }
catch (
const std::exception&
e) {
837 CVLog::Error(QString(
"[cvSelectionExporter] Exception while saving: %1")
848 CVLog::Error(
"[cvSelectionExporter] Object is nullptr");
854 settings.beginGroup(
"SelectionExport");
855 QString currentPath =
856 settings.value(
"LastPath", QDir::homePath()).toString();
857 QString defaultName =
object->getName();
860 QString settingsKey = isMesh ?
"LastFilterMesh" :
"LastFilterCloud";
861 QString lastFilter = settings.value(settingsKey).toString();
864 QStringList fileFilters;
865 QString selectedFilter = lastFilter;
868 bool canSave =
false;
871 bool isExclusive =
true;
872 bool multiple =
false;
875 bool isExclusive =
true;
876 bool multiple =
false;
883 QStringList filterStrs = filter->getFileFilters(
false);
884 fileFilters << filterStrs;
887 if (selectedFilter.isEmpty() && !filterStrs.isEmpty()) {
888 selectedFilter = filterStrs.first();
893 if (fileFilters.isEmpty()) {
894 CVLog::Error(
"[cvSelectionExporter] No suitable file filter found");
900 QString fullPath = currentPath +
"/" + defaultName;
901 QString selectedFilename = QFileDialog::getSaveFileName(
902 parent, QObject::tr(
"Export Selection to File"), fullPath,
903 fileFilters.join(
";;"), &selectedFilter);
905 if (selectedFilename.isEmpty()) {
916 parameters, selectedFilter);
919 CVLog::Error(QString(
"[cvSelectionExporter] Failed to save file: %1")
920 .arg(selectedFilename));
926 currentPath = QFileInfo(selectedFilename).absolutePath();
927 settings.setValue(
"LastPath", currentPath);
928 settings.setValue(settingsKey, selectedFilter);
931 CVLog::Print(QString(
"[cvSelectionExporter] Selection exported to: %1")
932 .arg(selectedFilename));
943 vtkPolyData* polyData,
944 const QList<cvSelectionData>& selections,
945 const QString& baseName) {
946 QList<ccMesh*> meshes;
948 if (!polyData || selections.isEmpty()) {
950 "[cvSelectionExporter] Invalid parameters for batch export");
956 if (selection.isEmpty()) {
958 QString(
"[cvSelectionExporter] Skipping empty selection %1")
973 QString(
"%1_%2").arg(baseName).arg(index, 3, 10, QChar(
'0'));
980 CVLog::Print(QString(
"[cvSelectionExporter] Batch exported mesh "
983 .arg(selections.size())
987 QString(
"[cvSelectionExporter] Failed to export mesh %1")
995 QString(
"[cvSelectionExporter] Batch export complete: %1/%2 meshes")
997 .arg(selections.size()));
1004 vtkPolyData* polyData,
1005 const QList<cvSelectionData>& selections,
1006 const QString& baseName) {
1007 QList<ccPointCloud*> clouds;
1009 if (!polyData || selections.isEmpty()) {
1011 "[cvSelectionExporter] Invalid parameters for batch export");
1017 if (selection.isEmpty()) {
1019 QString(
"[cvSelectionExporter] Skipping empty selection %1")
1026 CVLog::Warning(QString(
"[cvSelectionExporter] Skipping non-point "
1034 QString(
"%1_%2").arg(baseName).arg(index, 3, 10, QChar(
'0'));
1040 clouds.append(cloud);
1041 CVLog::Print(QString(
"[cvSelectionExporter] Batch exported cloud "
1044 .arg(selections.size())
1048 QString(
"[cvSelectionExporter] Failed to export cloud %1")
1056 QString(
"[cvSelectionExporter] Batch export complete: %1/%2 clouds")
1058 .arg(selections.size()));
1065 vtkPolyData* polyData,
1066 const QList<cvSelectionData>& selections,
1067 const QString& outputDir,
1069 const QString& baseName,
1070 std::function<
void(
int)> progressCallback) {
1071 if (!polyData || selections.isEmpty() || outputDir.isEmpty()) {
1073 "[cvSelectionExporter] Invalid parameters for batch export to "
1080 if (!dir.exists(outputDir)) {
1081 if (!dir.mkpath(outputDir)) {
1082 CVLog::Error(QString(
"[cvSelectionExporter] Failed to create "
1083 "output directory: %1")
1089 int successCount = 0;
1090 int totalCount = selections.size();
1092 for (
int i = 0; i < totalCount; ++i) {
1098 QString(
"[cvSelectionExporter] Skipping empty selection %1")
1100 if (progressCallback) {
1101 progressCallback((index * 100) / totalCount);
1107 QString
filename = QString(
"%1/%2_%3.%4")
1110 .arg(index, 3, 10, QChar(
'0'))
1115 polyData, selection,
filename,
false,
false);
1119 CVLog::Print(QString(
"[cvSelectionExporter] Exported %1/%2: %3")
1124 CVLog::Error(QString(
"[cvSelectionExporter] Failed to export %1/%2")
1130 if (progressCallback) {
1131 progressCallback((index * 100) / totalCount);
1135 CVLog::Print(QString(
"[cvSelectionExporter] Batch export complete: %1/%2 "
1136 "files exported to %3")
1141 return successCount;
1147 const QString& outputPath,
1149 if (!polyData || selection.
isEmpty() || outputPath.isEmpty()) {
1154 QString
filename = outputPath.arg(number, 3, 10, QChar(
'0'));
1158 QString
format = fileInfo.suffix().toLower();
filament::Texture::InternalFormat format
CC_FILE_ERROR
Typical I/O filter errors.
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
static bool Print(const char *format,...)
Prints out a formatted message in console.
static bool PrintVerbose(const char *format,...)
Prints out a verbose formatted message in console.
static bool Error(const char *format,...)
Display an error dialog with formatted message.
static CC_FILE_ERROR SaveToFile(ccHObject *entities, const QString &filename, const SaveParameters ¶meters, Shared filter)
static const FilterContainer & GetFilters()
Returns the set of all registered filters.
static Shared FindBestFilterForExtension(const QString &ext)
Returns the best filter (presumably) to open a given file extension.
QSharedPointer< FileIOFilter > Shared
Shared type.
static void DisplayErrorMessage(CC_FILE_ERROR err, const QString &action, const QString &filename)
Displays (to console) the message corresponding to a given error code.
virtual bool colorsShown() const
Returns whether colors are shown or not.
virtual bool sfShown() const
Returns whether active scalar field is visible.
virtual bool materialsShown() const
Sets whether textures/material should be displayed or not.
Hierarchical CLOUDVIEWER Object.
bool normalsShown() const override
Returns whether normals are shown or not.
virtual unsigned size() const override
Returns the number of triangles.
ccMesh * partialClone(const std::vector< unsigned > &triangleIndices, int *warnings=nullptr) const
Creates a new mesh from a selection of triangles (partial clone)
virtual void setName(const QString &name)
Sets object name.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
@ WRN_OUT_OF_MEM_FOR_COLORS
@ WRN_OUT_OF_MEM_FOR_NORMALS
virtual unsigned size() const =0
Returns the number of points.
unsigned size() const override
A very simple point cloud (no point duplication)
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
virtual bool reserve(unsigned n)
Reserves some memory for hosting the point references.
Encapsulates selection data without exposing VTK types.
FieldAssociation fieldAssociation() const
Get field association.
vtkSmartPointer< vtkIdTypeArray > vtkArray() const
Get the underlying VTK array (for internal use only)
@ CELLS
Selection applies to cells.
@ POINTS
Selection applies to points.
bool isEmpty() const
Check if selection is empty.
QVector< qint64 > ids() const
Get selected IDs as a vector (copy)
int count() const
Get number of selected items.
static bool exportToFile(vtkPolyData *polyData, const cvSelectionData &selectionData, const QString &filename, bool writeAscii=false, bool compressed=false)
Export selection to file (uses CV_io module)
static bool exportNumbered(vtkPolyData *polyData, const cvSelectionData &selection, const QString &outputPath, int number)
Export selection with numbered naming.
static ccPointCloud * exportToPointCloud(vtkPolyData *polyData, const cvSelectionData &selectionData, const ExportOptions &options=ExportOptions())
Export selected points to ccPointCloud.
static QList< ccMesh * > batchExportToMeshes(vtkPolyData *polyData, const QList< cvSelectionData > &selections, const QString &baseName="Selection")
Export multiple selections to meshes.
static QList< ccPointCloud * > batchExportToPointClouds(vtkPolyData *polyData, const QList< cvSelectionData > &selections, const QString &baseName="Selection")
Export multiple selections to point clouds.
static ccMesh * exportFromSourceMesh(ccMesh *sourceMesh, const cvSelectionData &selectionData, const ExportOptions &options=ExportOptions())
Export selected cells directly from source ccMesh.
static ccMesh * exportToMesh(vtkPolyData *polyData, const cvSelectionData &selectionData, const ExportOptions &options=ExportOptions())
Export selected cells to ccMesh.
static int batchExportToFiles(vtkPolyData *polyData, const QList< cvSelectionData > &selections, const QString &outputDir, const QString &format, const QString &baseName="selection", std::function< void(int)> progressCallback=nullptr)
Export multiple selections to files.
static vtkPolyData * extractSelection(vtkPolyData *polyData, const cvSelectionData &selectionData)
Extract selected geometry as new vtkPolyData.
static ccPointCloud * exportFromSourceCloud(ccPointCloud *sourceCloud, const cvSelectionData &selectionData, const ExportOptions &options=ExportOptions())
Export selected points directly from source ccPointCloud.
static bool saveObjectToFileWithDialog(ccHObject *object, bool isMesh, QWidget *parent=nullptr)
Save a ccHObject to file with file dialog Uses QFileDialog to let user choose filename and format Rem...
static ccMesh * ConvertToMesh(vtkPolyData *polydata, bool silent=false)
static ccPointCloud * ConvertToPointCloud(vtkPolyData *polydata, bool silent=false)
bool WriteEntity(const std::string &filename, const ccHObject &obj, bool write_ascii=false, bool compressed=false, bool print_progress=false)
Generic saving parameters.
QWidget * parentWidget
Parent widget (if any)
bool alwaysDisplaySaveDialog
Options for exporting selections.