35 #include <ui_commandLineDlg.h>
187 bool allowMinusOne =
false) {
191 cmd.
print(QObject::tr(
"SF index: LAST"));
193 bool validInt =
false;
194 sfIndex = sfName.toInt(&validInt);
199 if (allowMinusOne && sfIndex == -1) {
201 cmd.
print(QObject::tr(
"SF index: none"));
205 QObject::tr(
"Invalid SF index: %1").arg(sfIndex));
209 cmd.
print(QObject::tr(
"SF index: %1").arg(sfIndex));
212 cmd.
print(QObject::tr(
"SF name: '%1'").arg(sfName));
222 const QString& sfName,
223 bool minusOneMeansCurrent =
false) {
229 }
else if (sfIndex == -2) {
231 }
else if (sfIndex == -1) {
232 if (!sfName.isEmpty())
237 sfName.toStdString().c_str());
244 }
else if (minusOneMeansCurrent) {
248 QObject::tr(
"Input scalar field index is invalid: %1")
253 CVLog::Warning(QObject::tr(
"Cloud %1 has less scalar fields than the "
266 const QString& sfName,
267 bool minusOneMeansCurrent =
false) {
277 const QString& keyword)
287 QString argument = cmd.
arguments().front().toUpper();
293 for (
const auto& filter : filters) {
294 if (argument == filter->getDefaultExtension().toUpper()) {
296 fileFilter = filter->getFileFilters(
false)
300 defaultExt = filter->getDefaultExtension();
306 if (fileFilter.isEmpty()) {
307 cmd.
error(QObject::tr(
"Unhandled format specifier (%1)")
311 cmd.
error(QObject::tr(
"Missing file format specifier!"));
324 if (fileFilter.isEmpty()) {
329 cmd.
print(QObject::tr(
"Output export format (clouds) set to: %1")
330 .arg(defaultExt.toUpper()));
345 QString argument = cmd.
arguments().front();
353 QObject::tr(
"Missing parameter: extension after '%1'")
359 cmd.
print(QObject::tr(
"New output extension for clouds: %1")
369 "Missing parameter: precision value after '%1'")
373 int precision = cmd.
arguments().takeFirst().toInt(&ok);
374 if (!ok || precision < 0) {
376 QObject::tr(
"Invalid value for precision! (%1)")
381 cmd.
warning(QObject::tr(
"Argument '%1' is only applicable to "
394 return cmd.
error(QObject::tr(
"Missing parameter: separator "
395 "character after '%1'")
400 cmd.
warning(QObject::tr(
"Argument '%1' is only applicable to "
405 QString separatorStr = cmd.
arguments().takeFirst().toUpper();
408 if (separatorStr ==
"SPACE") {
410 }
else if (separatorStr ==
"SEMICOLON") {
412 }
else if (separatorStr ==
"COMMA") {
414 }
else if (separatorStr ==
"TAB") {
417 return cmd.
error(QObject::tr(
"Invalid separator! ('%1')")
428 cmd.
warning(QObject::tr(
"Argument '%1' is only applicable to "
440 cmd.
warning(QObject::tr(
"Argument '%1' is only applicable to "
462 if (fileFilter.isEmpty()) {
467 cmd.
print(QObject::tr(
"Output export format (meshes) set to: %1")
468 .arg(defaultExt.toUpper()));
472 QString argument = cmd.
arguments().front();
481 QObject::tr(
"Missing parameter: extension after '%1'")
487 cmd.
print(QObject::tr(
"New output extension for meshes: %1")
505 if (fileFilter.isEmpty()) {
510 cmd.
print(QObject::tr(
"Output export format (hierarchy) set to: %1")
511 .arg(defaultExt.toUpper()));
515 QString argument = cmd.
arguments().front();
524 QObject::tr(
"Missing parameter: extension after '%1'")
530 cmd.
print(QObject::tr(
"New output extension for hierarchies: %1")
545 cmd.
print(QObject::tr(
"[LOADING]"));
548 QObject::tr(
"Missing parameter: filename after \"-%1\"")
557 QString argument = cmd.
arguments().front();
566 "Missing parameter: number of lines after '%1'")
571 skipLines = cmd.
arguments().takeFirst().toInt(&ok);
575 "Invalid parameter: number of lines after '%1'")
579 cmd.
print(QObject::tr(
"Will skip %1 lines").arg(skipLines));
593 if (skipLines >= 0) {
611 cmd.
print(QObject::tr(
"[CLEAR NORMALS]"));
615 "No entity loaded (be sure to open at least one file "
616 "with \"-%1 [cloud filename]\" before \"-%2\")")
660 cmd.
print(QObject::tr(
"[INVERT NORMALS]"));
664 QObject::tr(
"No input point cloud or mesh (be sure to open one "
665 "with \"-%1 [cloud filename]\" before \"-%2\")")
673 cmd.
warning(QObject::tr(
"Cloud %1 has no normals")
683 if (!errorStr.isEmpty()) {
684 return cmd.
error(errorStr);
698 QObject::tr(
"Mesh %1 has no normals").arg(mesh->
getName()));
707 if (!errorStr.isEmpty()) {
708 return cmd.
error(errorStr);
718 QObject::tr(
"Compute normals with octree"),
722 cmd.
print(QObject::tr(
"[OCTREE NORMALS CALCULATION]"));
723 if (cmd.
clouds().empty()) {
726 "No point cloud to compute normals (be sure to open "
727 "one with \"-%1 [cloud filename]\" before \"-%2\")")
732 return cmd.
error(QObject::tr(
"Missing parameter: radius after \"-%1\"")
736 float radius = std::numeric_limits<float>::quiet_NaN();
737 QString radiusArg = cmd.
arguments().takeFirst();
738 if (radiusArg.toUpper() !=
"AUTO") {
740 radius = radiusArg.toFloat(&ok);
742 return cmd.
error(QObject::tr(
"Invalid radius"));
745 cmd.
print(QObject::tr(
"\tRadius: %1").arg(radiusArg));
749 ccNormalVectors::Orientation::UNDEFINED;
752 QString argument = cmd.
arguments().front().toUpper();
756 QString orient_argument = cmd.
arguments().takeFirst().toUpper();
757 if (orient_argument ==
"PLUS_ZERO" ||
758 orient_argument ==
"PLUS_ORIGIN") {
759 orientation = ccNormalVectors::Orientation::PLUS_ORIGIN;
760 }
else if (orient_argument ==
"MINUS_ZERO" ||
761 orient_argument ==
"MINUS_ORIGIN") {
762 orientation = ccNormalVectors::Orientation::MINUS_ORIGIN;
763 }
else if (orient_argument ==
"PLUS_BARYCENTER") {
764 orientation = ccNormalVectors::Orientation::PLUS_BARYCENTER;
765 }
else if (orient_argument ==
"MINUS_BARYCENTER") {
767 ccNormalVectors::Orientation::MINUS_BARYCENTER;
768 }
else if (orient_argument ==
"PLUS_X") {
769 orientation = ccNormalVectors::Orientation::PLUS_X;
770 }
else if (orient_argument ==
"MINUS_X") {
771 orientation = ccNormalVectors::Orientation::MINUS_X;
772 }
else if (orient_argument ==
"PLUS_Y") {
773 orientation = ccNormalVectors::Orientation::PLUS_Y;
774 }
else if (orient_argument ==
"MINUS_Y") {
775 orientation = ccNormalVectors::Orientation::MINUS_Y;
776 }
else if (orient_argument ==
"PLUS_Z") {
777 orientation = ccNormalVectors::Orientation::PLUS_Z;
778 }
else if (orient_argument ==
"MINUS_Z") {
779 orientation = ccNormalVectors::Orientation::MINUS_Z;
780 }
else if (orient_argument ==
"PREVIOUS") {
781 orientation = ccNormalVectors::Orientation::PREVIOUS;
782 }
else if (orient_argument ==
"PLUS_SENSOR_ORIGIN") {
784 ccNormalVectors::Orientation::PLUS_SENSOR_ORIGIN;
785 }
else if (orient_argument ==
"MINUS_SENSOR_ORIGIN") {
787 ccNormalVectors::Orientation::MINUS_SENSOR_ORIGIN;
789 return cmd.
error(QObject::tr(
"Invalid parameter: unknown "
791 .arg(orient_argument));
794 return cmd.
error(QObject::tr(
"Missing orientation"));
799 QString model_arg = cmd.
arguments().takeFirst().toUpper();
800 if (model_arg ==
"LS") {
802 }
else if (model_arg ==
"TRI") {
804 }
else if (model_arg ==
"QUADRIC") {
808 QObject::tr(
"Invalid parameter: unknown model '%1'")
812 return cmd.
error(QObject::tr(
"Missing model"));
822 QScopedPointer<ecvProgressDialog> progressDialog(
nullptr);
824 progressDialog.reset(
826 progressDialog->setAutoClose(
false);
832 QObject::tr(
"Failed to compute octree for cloud '%1'")
837 float thisCloudRadius = radius;
838 if (std::isnan(thisCloudRadius)) {
841 if (thisCloudRadius == 0) {
842 return cmd.
error(QObject::tr(
"Failed to determine best normal "
843 "radius for cloud '%1'")
846 cmd.
print(QObject::tr(
"\tCloud %1 radius = %2")
848 .arg(thisCloudRadius));
851 cmd.
print(QObject::tr(
"computeNormalsWithOctree started..."));
853 model, orientation, thisCloudRadius, progressDialog.data());
855 cmd.
print(QObject::tr(
"computeNormalsWithOctree success"));
856 cmd.
print(QObject::tr(
"cloud->hasNormals: %1")
859 return cmd.
error(QObject::tr(
"computeNormalsWithOctree failed"));
864 CLCloudDesc cloudDesc(cloud, thisCloudDesc.basename,
866 thisCloudDesc.indexInFile);
867 QString errorStr = cmd.
exportEntity(cloudDesc,
"OCTREE_NORMALS");
868 if (!errorStr.isEmpty()) {
869 return cmd.
error(errorStr);
879 QObject::tr(
"Convert normals to dip and dip. dir."),
883 cmd.
print(QObject::tr(
"[CONVERT NORMALS TO DIP/DIP DIR]"));
884 if (cmd.
clouds().empty()) {
886 QObject::tr(
"No input point cloud (be sure to open one with "
887 "\"-%1 [cloud filename]\" before \"-%2\")")
895 cmd.
warning(QObject::tr(
"Cloud %1 has no normals")
901 container.push_back(cloud);
905 return cmd.
error(QObject::tr(
906 "Failed to convert normals to dip and dip direction"));
912 if (!errorStr.isEmpty()) {
913 return cmd.
error(errorStr);
923 QObject::tr(
"Convert normals to scalar fields"),
927 cmd.
print(QObject::tr(
"[CONVERT NORMALS TO SCALAR FIELD(S)]"));
928 if (cmd.
clouds().empty()) {
930 QObject::tr(
"No input point cloud (be sure to open one with "
931 "\"-%1 [cloud filename]\" before \"-%2\")")
939 cmd.
warning(QObject::tr(
"Cloud %1 has no normals")
944 bool exportDims[3] = {
true,
true,
true};
947 container.push_back(cloud);
951 QObject::tr(
"Failed to convert normals to scalar fields"));
955 QString errorStr = cmd.
exportEntity(thisCloudDesc,
"_NORM_TO_SF");
956 if (!errorStr.isEmpty()) {
957 return cmd.
error(errorStr);
970 cmd.
print(QObject::tr(
"[SUBSAMPLING]"));
971 if (cmd.
clouds().empty()) {
973 QObject::tr(
"No point cloud to resample (be sure to open one "
974 "with \"-%1 [cloud filename]\" before \"-%2\")")
981 "Missing parameter: resampling method after \"-%1\"")
985 QString method = cmd.
arguments().takeFirst().toUpper();
986 cmd.
print(QObject::tr(
"\tMethod: ") + method);
987 if (method ==
"RANDOM") {
989 return cmd.
error(QObject::tr(
"Missing parameter: number of points "
990 "after \"-%1 RANDOM\"")
997 return cmd.
error(QObject::tr(
998 "Invalid number of points for random resampling!"));
1000 cmd.
print(QObject::tr(
"\tOutput points: %1").arg(
count));
1002 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
1004 cmd.
print(QObject::tr(
"\tProcessing cloud #%1 (%2)")
1006 .arg(!cloud->
getName().isEmpty()
1014 return cmd.
error(QObject::tr(
"Subsampling process failed!"));
1016 cmd.
print(QObject::tr(
"\tResult: %1 points").arg(refCloud->
size()));
1025 QObject::tr(
".subsampled"));
1029 cmd.
clouds()[i].indexInFile);
1032 if (!errorStr.isEmpty()) {
1034 return cmd.
error(errorStr);
1038 delete cmd.
clouds()[i].pc;
1040 cmd.
clouds()[i].basename += QObject::tr(
"_SUBSAMPLED");
1044 return cmd.
error(QObject::tr(
"Not enough memory!"));
1047 }
else if (method ==
"SPATIAL") {
1049 return cmd.
error(QObject::tr(
"Missing parameter: spatial step "
1050 "after \"-%1 SPATIAL\"")
1054 double step = cmd.
arguments().takeFirst().toDouble(&ok);
1055 if (!ok || step <= 0) {
1057 QObject::tr(
"Invalid step value for spatial resampling!"));
1059 cmd.
print(QObject::tr(
"\tSpatial step: %1").arg(step));
1061 double sfMinSpacing = 0;
1062 double sfMaxSpacing = 0;
1063 bool useActiveSF =
false;
1070 bool validMin =
false;
1072 cmd.
arguments().takeFirst().toDouble(&validMin);
1073 bool validMax =
false;
1075 cmd.
arguments().takeFirst().toDouble(&validMax);
1076 if (!validMin || !validMax || sfMinSpacing < 0 ||
1080 "Invalid parameters: Two positive "
1081 "decimal number required after '%1'")
1086 QObject::tr(
"Missing parameters: Two positive "
1087 "decimal number required after '%1'")
1094 cmd.
print(QObject::tr(
"\tProcessing cloud %1")
1095 .arg(!desc.pc->getName().isEmpty()
1096 ? desc.pc->getName()
1105 ccScalarField* sf = desc.pc->getCurrentDisplayedScalarField();
1110 "\tCan't use 'Use active SF': no active "
1111 "scalar field. Set one with '-%1'")
1122 sfMax < sf->getMax())
1129 QObject::tr(
"\tCan't use 'Use active SF': "
1130 "scalar field '%1' has invalid "
1132 .arg(QString::fromStdString(
1140 double deltaSF =
static_cast<double>(sfMax) -
1141 static_cast<double>(sfMin);
1145 (sfMaxSpacing - sfMinSpacing) / deltaSF;
1147 sfMinSpacing - modParams.
a * sfMin;
1150 modParams.
b = sfMin;
1152 cmd.
print(QObject::tr(
"\tUse active SF: "
1153 "enabled\n\t\tSpacing at SF "
1154 "min (%1): %2\n\t\tSpacing "
1155 "at SF max (%3): %4")
1159 .arg(sfMaxSpacing));
1163 cmd.
warning(QObject::tr(
"\tCan't use 'Use active SF': "
1164 "scalar field '%2' does not "
1165 "have any valid value.")
1167 .arg(QString::fromStdString(
1173 if (useActiveSF && !modParams.
enabled) {
1175 QObject::tr(
"\t'Use active SF' disabled. Falling back "
1176 "to constant spacing."));
1184 return cmd.
error(
"Subsampling process failed!");
1186 cmd.
print(QObject::tr(
"\tResult: %1 points").arg(refCloud->
size()));
1194 result->setName(desc.pc->getName() +
1195 QObject::tr(
".subsampled"));
1201 if (!errorStr.isEmpty()) {
1203 return cmd.
error(errorStr);
1209 desc.basename +=
"_SPATIAL_SUBSAMPLED";
1211 return cmd.
error(QObject::tr(
"Not enough memory!"));
1214 }
else if (method ==
"OCTREE") {
1216 return cmd.
error(QObject::tr(
"Missing parameter: octree level "
1217 "after \"-%1 OCTREE\"")
1225 return cmd.
error(QObject::tr(
"Invalid octree level!"));
1229 QScopedPointer<ecvProgressDialog> progressDialog(
nullptr);
1231 progressDialog.reset(
1233 progressDialog->setAutoClose(
false);
1236 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
1238 cmd.
print(QObject::tr(
"\tProcessing cloud #%1 (%2)")
1240 .arg(!cloud->
getName().isEmpty()
1248 NEAREST_POINT_TO_CELL_CENTER,
1249 progressDialog.data());
1251 return cmd.
error(QObject::tr(
"Subsampling process failed!"));
1253 cmd.
print(QObject::tr(
"\tResult: %1 points").arg(refCloud->
size()));
1262 QObject::tr(
".subsampled"));
1266 cmd.
clouds()[i].indexInFile);
1268 cloudDesc, QObject::tr(
"OCTREE_LEVEL_%1_SUBSAMPLED")
1270 if (!errorStr.isEmpty()) {
1272 return cmd.
error(errorStr);
1276 delete cmd.
clouds()[i].pc;
1278 cmd.
clouds()[i].basename += QObject::tr(
"_SUBSAMPLED");
1282 return cmd.
error(QObject::tr(
"Not enough memory!"));
1286 if (progressDialog) {
1287 progressDialog->close();
1288 QCoreApplication::processEvents();
1291 return cmd.
error(QObject::tr(
"Unknown method!"));
1302 cmd.
print(QObject::tr(
"[CONNECTED COMPONENTS EXTRACTION]"));
1303 if (cmd.
clouds().empty()) {
1305 QObject::tr(
"No point cloud loaded (be sure to open one with "
1306 "\"-%1 [cloud filename]\" before \"-%2\")")
1313 QObject::tr(
"Missing parameter: octree level after \"-%1\"")
1318 std::min<unsigned char>(cmd.
arguments().takeFirst().toUShort(&ok),
1321 return cmd.
error(QObject::tr(
"Invalid octree level!"));
1328 QObject::tr(
"Missing parameter: minimum number of points per "
1329 "component after \"-%1 [octree level]\"")
1332 unsigned minPointCount = cmd.
arguments().takeFirst().toUInt(&ok);
1334 return cmd.
error(QObject::tr(
"Invalid min. number of points!"));
1336 cmd.
print(QObject::tr(
"\tMin number of points per component: %1")
1337 .arg(minPointCount));
1340 QScopedPointer<ecvProgressDialog> progressDialog(
nullptr);
1342 progressDialog.reset(
1344 progressDialog->setAutoClose(
false);
1347 std::vector<CLCloudDesc> inputClouds = cmd.
clouds();
1349 for (
size_t i = 0; i < inputClouds.size(); ++i) {
1351 cmd.
print(QObject::tr(
"\tProcessing cloud #%1 (%2)")
1353 .arg(!cloud->
getName().isEmpty()
1365 cmd.
error(QObject::tr(
1366 "Couldn't allocate a new scalar field for computing CC "
1367 "labels! Try to free some memory ..."));
1376 false, progressDialog.data());
1378 if (componentCount == 0) {
1379 cmd.
error(QObject::tr(
"No component found!"));
1392 "An error occurred (failed to finish the extraction)"));
1398 for (
size_t j = 0; j < components.size(); ++j) {
1402 if (compIndexes->
size() >= minPointCount) {
1414 inputClouds[i].basename +
1415 QObject::tr(
"_COMPONENT_%1")
1417 inputClouds[i].
path);
1420 cloudDesc, QString(),
nullptr,
1423 if (!errorStr.isEmpty()) {
1424 cmd.
error(errorStr);
1428 cmd.
clouds().push_back(cloudDesc);
1430 cmd.
warning(QObject::tr(
"Failed to create component "
1431 "#%1! (not enough memory)")
1437 compIndexes =
nullptr;
1442 if (cmd.
clouds().empty()) {
1443 cmd.
error(QObject::tr(
1444 "No component was created! Check the minimum size..."));
1446 cmd.
print(QObject::tr(
"%1 component(s) were created")
1447 .arg(cmd.
clouds().size()));
1451 if (progressDialog) {
1452 progressDialog->close();
1453 QCoreApplication::processEvents();
1455 }
catch (
const std::bad_alloc&) {
1456 cmd.
error(QObject::tr(
"Not enough memory"));
1468 cmd.
print(QObject::tr(
"[CURVATURE]"));
1472 QObject::tr(
"Missing parameter: curvature type after \"-%1\"")
1476 QString curvTypeStr = cmd.
arguments().takeFirst().toUpper();
1479 if (curvTypeStr ==
"MEAN") {
1481 }
else if (curvTypeStr ==
"GAUSS") {
1483 }
else if (curvTypeStr ==
"NORMAL_CHANGE") {
1486 return cmd.
error(QObject::tr(
"Invalid curvature type after \"-%1\". "
1487 "Got '%2' instead of MEAN or GAUSS.")
1492 return cmd.
error(QObject::tr(
1493 "Missing parameter: kernel size after curvature type"));
1496 bool paramOk =
false;
1497 QString kernelStr = cmd.
arguments().takeFirst();
1502 QObject::tr(
"Failed to read a numerical parameter: kernel size "
1503 "(after curvature type). Got '%1' instead.")
1506 cmd.
print(QObject::tr(
"\tKernel size: %1").arg(kernelSize));
1508 if (cmd.
clouds().empty()) {
1509 return cmd.
error(QObject::tr(
"No point cloud on which to compute "
1510 "curvature! (be sure to open one with "
1511 "\"-%1 [cloud filename]\" before \"-%2\")")
1517 entities.resize(cmd.
clouds().size());
1518 for (
size_t i = 0; i < cmd.
clouds().size(); ++i) {
1519 entities[i] = cmd.
clouds()[i].pc;
1527 !cmd.
saveClouds(QObject::tr(
"%1_CURVATURE_KERNEL_%2")
1529 .arg(kernelSize))) {
1540 return cmd.
error(QObject::tr(
"Missing parameter: density type after "
1541 "\"-%1\" (KNN/SURFACE/VOLUME)")
1546 QString typeArg = cmd.
arguments().takeFirst().toUpper();
1547 if (typeArg ==
"KNN") {
1549 }
else if (typeArg ==
"SURFACE") {
1551 }
else if (typeArg ==
"VOLUME") {
1555 QObject::tr(
"Invalid parameter: density type is expected after "
1556 "\"-%1\" (KNN/SURFACE/VOLUME)")
1568 cmd.
print(QObject::tr(
"[APPROX DENSITY]"));
1569 if (cmd.
clouds().empty()) {
1571 QObject::tr(
"No point cloud on which to compute approx. "
1572 "density! (be sure to open one with \"-%1 [cloud "
1573 "filename]\" before \"-%2\")")
1579 entities.resize(cmd.
clouds().size());
1580 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
1581 entities[i] = cmd.
clouds()[i].pc;
1588 QString argument = cmd.
arguments().front();
1594 QObject::tr(
"Missing parameter: density type after "
1595 "\"-%1\" (KNN/SURFACE/VOLUME)")
1607 densityType, 0, entities,
nullptr, cmd.
widgetParent())) {
1622 cmd.
print(QObject::tr(
"[DENSITY]"));
1626 QObject::tr(
"Missing parameter: sphere radius after \"-%1\"")
1630 bool paramOk =
false;
1631 QString kernelStr = cmd.
arguments().takeFirst();
1636 QObject::tr(
"Failed to read a numerical parameter: sphere "
1637 "radius (after \"-%1\"). Got '%2' instead.")
1640 cmd.
print(QObject::tr(
"\tSphere radius: %1").arg(kernelSize));
1646 QString argument = cmd.
arguments().front();
1652 QObject::tr(
"Missing parameter: density type after "
1653 "\"-%1\" (KNN/SURFACE/VOLUME)")
1663 if (cmd.
clouds().empty()) {
1664 return cmd.
error(QObject::tr(
"No point cloud on which to compute "
1665 "density! (be sure to open one with \"-%1 "
1666 "[cloud filename]\" before \"-%2\")")
1672 entities.resize(cmd.
clouds().size());
1673 for (
size_t i = 0; i < cmd.
clouds().size(); ++i) {
1674 entities[i] = cmd.
clouds()[i].pc;
1679 densityType, kernelSize, entities,
nullptr,
1695 cmd.
print(QObject::tr(
"[SF GRADIENT]"));
1698 return cmd.
error(QObject::tr(
"Missing parameter: boolean (whether SF "
1699 "is euclidean or not) after \"-%1\"")
1703 QString euclideanStr = cmd.
arguments().takeFirst().toUpper();
1704 bool euclidean =
false;
1705 if (euclideanStr ==
"TRUE") {
1707 }
else if (euclideanStr !=
"FALSE") {
1708 return cmd.
error(QObject::tr(
"Invalid boolean value after \"-%1\". Got "
1709 "'%2' instead of TRUE or FALSE.")
1713 if (cmd.
clouds().empty()) {
1714 return cmd.
error(QObject::tr(
"No point cloud on which to compute SF "
1715 "gradient! (be sure to open one with "
1716 "\"-%1 [cloud filename]\" before \"-%2\")")
1721 void* additionalParameters[1] = {&euclidean};
1723 entities.reserve(cmd.
clouds().size());
1724 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
1725 unsigned sfCount = cmd.
clouds()[i].pc->getNumberOfScalarFields();
1727 cmd.
warning(QObject::tr(
"cmd.warning: cloud '%1' has no scalar "
1728 "field (it will be ignored)")
1729 .arg(cmd.
clouds()[i].pc->getName()));
1734 "cmd.warning: cloud '%1' has several scalar "
1735 "fields (the active one will be used by "
1736 "default, or the first one if none is active)")
1737 .arg(cmd.
clouds()[i].pc->getName()));
1741 cmd.
clouds()[i].pc->getCurrentOutScalarFieldIndex();
1742 if (activeSFIndex < 0) {
1746 cmd.
clouds()[i].pc->setCurrentDisplayedScalarField(activeSFIndex);
1748 entities.push_back(cmd.
clouds()[i].pc);
1757 !cmd.
saveClouds(euclidean ?
"EUCLIDEAN_SF_GRAD" :
"SF_GRAD")) {
1772 QObject::tr(
"Missing parameter: kernel size after \"-%1\"")
1776 bool paramOk =
false;
1777 QString kernelStr = cmd.
arguments().takeFirst();
1782 QObject::tr(
"Failed to read a numerical parameter: kernel size "
1783 "(after \"-%1\"). Got '%2' instead.")
1786 cmd.
print(QObject::tr(
"\tKernel size: %1").arg(kernelSize));
1792 QString nextArg = cmd.
arguments().first();
1793 if (nextArg.startsWith(
'-') &&
1797 QString xStr = cmd.
arguments().takeFirst();
1798 QString yStr = cmd.
arguments().takeFirst();
1799 QString zStr = cmd.
arguments().takeFirst();
1800 bool okX =
false, okY =
false, okZ =
false;
1807 if (!okX || !okY || !okZ) {
1809 QObject::tr(
"Invalid 'up direction' vector after "
1810 "option -%1 (3 coordinates expected)")
1813 _roughnessUpDir = &roughnessUpDir;
1817 if (cmd.
clouds().empty()) {
1818 return cmd.
error(QObject::tr(
"No point cloud on which to compute "
1819 "roughness! (be sure to open one with "
1820 "\"-%1 [cloud filename]\" before \"-%2\")")
1826 entities.resize(cmd.
clouds().size());
1827 for (
size_t i = 0; i < cmd.
clouds().size(); ++i) {
1828 entities[i] = cmd.
clouds()[i].pc;
1837 QObject::tr(
"ROUGHNESS_KERNEL_%2").arg(kernelSize))) {
1850 cmd.
print(QObject::tr(
"[APPLY TRANSFORMATION]"));
1855 "Missing parameter: transformation file after \"-%1\"")
1863 QObject::tr(
"Failed to read transformation matrix file '%1'!")
1867 cmd.
print(QObject::tr(
"Transformation:\n") + mat.
toString(6));
1871 QObject::tr(
"No entity on which to apply the transformation! "
1872 "(be sure to open one with \"-%1 [filename]\" "
1878 if (!cmd.
clouds().empty()) {
1880 desc.pc->applyGLTransformation_recursive(&mat);
1881 desc.pc->setName(desc.pc->getName() +
".transformed");
1888 if (!cmd.
meshes().empty()) {
1890 desc.mesh->applyGLTransformation_recursive(&mat);
1891 desc.mesh->setName(desc.mesh->getName() +
".transformed");
1907 cmd.
print(QObject::tr(
"[DROP GLOBAL SHIFT]"));
1910 return cmd.
error(QObject::tr(
"No loaded entity! (be sure to open one "
1911 "with \"-%1 [filename]\" before \"-%2\")")
1917 desc.pc->setGlobalShift(0, 0, 0);
1921 bool isLocked =
false;
1924 if (shifted && !isLocked) {
1937 cmd.
print(QObject::tr(
"[SF COLOR SCALE]"));
1941 QObject::tr(
"Missing parameter: color scale file after \"-%1\"")
1950 return cmd.
error(QObject::tr(
"Failed to read color scale file '%1'!")
1954 if (cmd.
clouds().empty()) {
1955 return cmd.
error(QObject::tr(
"No point cloud on which to change the SF "
1956 "color scale! (be sure to open one with "
1957 "\"-%1 [cloud filename]\" before \"-%2\")")
1961 for (
auto& cloud : cmd.
clouds()) {
1963 cloud.pc->getCurrentOutScalarField());
1981 cmd.
print(QObject::tr(
"[SF CONVERT TO RGB]"));
1985 QObject::tr(
"Missing parameter: boolean (whether to mix with "
1986 "existing colors or not) after \"-%1\"")
1990 QString mixWithExistingColorsStr = cmd.
arguments().takeFirst().toUpper();
1991 bool mixWithExistingColors =
false;
1992 if (mixWithExistingColorsStr ==
"TRUE") {
1993 mixWithExistingColors =
true;
1994 }
else if (mixWithExistingColorsStr !=
"FALSE") {
1995 return cmd.
error(QObject::tr(
"Invalid boolean value after \"-%1\". Got "
1996 "'%2' instead of TRUE or FALSE.")
1998 mixWithExistingColorsStr));
2001 if (cmd.
clouds().empty()) {
2002 return cmd.
error(QObject::tr(
"No point cloud on which to convert SF to "
2003 "RGB! (be sure to open one with \"-%1 "
2004 "[cloud filename]\" before \"-%2\")")
2008 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
2014 cmd.
warning(QObject::tr(
"cmd.warning: cloud '%1' has no scalar "
2015 "field (it will be ignored)")
2017 }
else if (activeSFIndex < 0) {
2018 cmd.
warning(QObject::tr(
"cmd.warning: cloud '%1' has no active "
2019 "scalar field (it will be ignored)")
2029 cmd.
warning(QObject::tr(
"cmd.warning: cloud '%1' failed to "
2030 "convert SF to RGB")
2063 cmd.
print(QObject::tr(
"[FILTER BY VALUE]"));
2066 ScalarType minVal = 0;
2071 QObject::tr(
"Missing parameter: min value after \"-%1\"")
2075 bool paramOk =
false;
2076 minValStr = cmd.
arguments().takeFirst();
2077 if (minValStr.toUpper() ==
"MIN") {
2079 }
else if (minValStr.toUpper() ==
"DISP_MIN") {
2081 }
else if (minValStr.toUpper() ==
"SAT_MIN") {
2083 }
else if (minValStr.toUpper() ==
"N_SIGMA_MIN") {
2086 return cmd.
error(QObject::tr(
"Missing parameter: N value "
2087 "(after \"-%1 N_SIGMA_MIN\").")
2090 minValStr = cmd.
arguments().takeFirst();
2091 minVal =
static_cast<ScalarType
>(minValStr.toDouble(¶mOk));
2095 "Failed to read a numerical parameter: N value "
2096 "(after \"N_SIGMA_MIN\"). Got '%2' instead.")
2100 minVal =
static_cast<ScalarType
>(minValStr.toDouble(¶mOk));
2103 QObject::tr(
"Failed to read a numerical parameter: min "
2104 "value (after \"-%1\"). Got '%2' instead.")
2111 ScalarType maxVal = 0;
2117 "Missing parameter: max value after \"-%1\" {min}")
2121 bool paramOk =
false;
2122 maxValStr = cmd.
arguments().takeFirst();
2123 if (maxValStr.toUpper() ==
"MAX") {
2125 }
else if (maxValStr.toUpper() ==
"DISP_MAX") {
2127 }
else if (maxValStr.toUpper() ==
"SAT_MAX") {
2129 }
else if (maxValStr.toUpper() ==
"N_SIGMA_MAX") {
2132 return cmd.
error(QObject::tr(
"Missing parameter: N value "
2133 "(after \"-%1 N_SIGMA_MAX\").")
2136 maxValStr = cmd.
arguments().takeFirst();
2137 maxVal =
static_cast<ScalarType
>(maxValStr.toDouble(¶mOk));
2141 "Failed to read a numerical parameter: N value "
2142 "(after \"N_SIGMA_MAX\"). Got '%2' instead.")
2146 maxVal =
static_cast<ScalarType
>(maxValStr.toDouble(¶mOk));
2150 "Failed to read a numerical parameter: max "
2151 "value (after min value). Got '%1' instead.")
2157 cmd.
print(QObject::tr(
"\tInterval: [%1 - %2]").arg(minValStr, maxValStr));
2159 if (cmd.
clouds().empty()) {
2161 QObject::tr(
"No point cloud on which to filter SF! (be sure to "
2162 "open one or generate one with \"-%1 [cloud "
2163 "filename]\" before \"-%2\")")
2167 for (
size_t i = 0; i < cmd.
clouds().size(); ++i) {
2169 cmd.
clouds()[i].pc->getCurrentOutScalarField();
2171 ScalarType thisMinVal = minVal;
2173 switch (useValForMin) {
2175 thisMinVal = sf->
getMin();
2189 ScalarType variance;
2191 thisMinVal = mean - (sqrt(variance) * minVal);
2199 ScalarType thisMaxVal = maxVal;
2201 switch (useValForMax) {
2203 thisMaxVal = sf->
getMax();
2217 ScalarType variance;
2219 thisMaxVal = mean + (sqrt(variance) * maxVal);
2228 cmd.
clouds()[i].pc->filterPointsByScalarValue(thisMinVal,
2230 if (fitleredCloud) {
2232 QObject::tr(
"\t\tCloud '%1' --> %2/%3 points remaining")
2233 .arg(cmd.
clouds()[i].pc->getName())
2234 .arg(fitleredCloud->
size())
2235 .arg(cmd.
clouds()[i].pc->size()));
2239 cmd.
clouds()[i].indexInFile);
2241 delete cmd.
clouds()[i].pc;
2242 cmd.
clouds()[i].pc = fitleredCloud;
2243 cmd.
clouds()[i].basename += QObject::tr(
"_FILTERED_[%1_%2]")
2248 if (!errorStr.isEmpty()) {
2249 delete fitleredCloud;
2250 return cmd.
error(errorStr);
2265 cmd.
print(QObject::tr(
"[COMPUTE MESH VOLUME]"));
2267 if (cmd.
meshes().empty()) {
2268 cmd.
warning(QObject::tr(
"No mesh loaded! Nothing to do..."));
2273 QString outputFilename;
2275 QString argument = cmd.
arguments().front();
2282 outputFilename = cmd.
arguments().front();
2284 cmd.
print(QObject::tr(
"Volume report file: %1")
2285 .arg(outputFilename));
2288 QObject::tr(
"Missing argument: filename after '%1'")
2295 QTextStream outStream(&outFile);
2296 if (!outputFilename.isEmpty()) {
2297 outFile.setFileName(outputFilename);
2298 if (!outFile.open(QFile::WriteOnly | QFile::Text)) {
2300 QObject::tr(
"Failed to create/open volume report file"));
2310 QString titleStr = QObject::tr(
"Mesh '%1'").arg(meshDesc.basename);
2311 if (meshDesc.indexInFile >= 0) {
2312 titleStr += QObject::tr(
" (#%2)").arg(meshDesc.indexInFile);
2314 cmd.
print(titleStr);
2315 QString volumeStr = QObject::tr(
"V = %2").arg(V, 0,
'f', 8);
2316 cmd.
print(volumeStr);
2318 if (outFile.isOpen()) {
2332 cmd.
print(QObject::tr(
"[MERGE MESHES]"));
2334 if (cmd.
meshes().size() < 2) {
2336 QObject::tr(
"Less than 2 meshes are loaded! Nothing to do..."));
2341 bool firstValidMesh =
true;
2345 QScopedPointer<ccMesh> mergedMesh(
new ccMesh(vertices));
2346 mergedMesh->setName(
"Merged mesh");
2347 mergedMesh->addChild(vertices);
2355 CVLog::Error(QObject::tr(
"Can't merge mesh '%1' (unhandled type)")
2356 .arg(meshDesc.basename));
2359 if (mergedMesh->merge(mesh,
true))
2361 if (firstValidMesh) {
2363 mergedMeshDesc = meshDesc;
2364 mergedMeshDesc.
mesh =
nullptr;
2365 firstValidMesh =
false;
2368 return cmd.
error(QObject::tr(
"Merge operation failed"));
2371 delete meshDesc.mesh;
2372 meshDesc.mesh =
nullptr;
2375 if (mergedMesh->size() == 0) {
2376 return cmd.
error(QObject::tr(
"Result is empty"));
2382 mergedMeshDesc.
basename += QObject::tr(
"_MERGED");
2383 mergedMeshDesc.
mesh = mergedMesh.take();
2384 cmd.
meshes().push_back(mergedMeshDesc);
2388 if (!errorStr.isEmpty()) {
2389 return cmd.
error(errorStr);
2401 cmd.
print(QObject::tr(
"[MERGE CLOUDS]"));
2403 if (cmd.
clouds().size() < 2) {
2405 QObject::tr(
"Less than 2 clouds are loaded! Nothing to do..."));
2411 for (
size_t i = 1; i < cmd.
clouds().
size(); ++i) {
2412 unsigned beforePts = cmd.
clouds().front().pc->size();
2413 unsigned newPts = cmd.
clouds()[i].pc->size();
2417 if (cmd.
clouds().front().pc->size() == beforePts + newPts) {
2418 delete cmd.
clouds()[i].pc;
2419 cmd.
clouds()[i].pc =
nullptr;
2422 QObject::tr(
"Fusion failed! (not enough memory?)"));
2430 cmd.
clouds().front().basename += QObject::tr(
"_MERGED");
2433 if (!errorStr.isEmpty()) {
2434 return cmd.
error(errorStr);
2448 "Missing parameter: scalar field index after \"-%1\"")
2452 bool paramOk =
false;
2453 QString sfIndexStr = cmd.
arguments().takeFirst();
2454 int sfIndex = sfIndexStr.toInt(¶mOk);
2457 QObject::tr(
"Failed to read a numerical parameter: S.F. index "
2458 "(after \"-%1\"). Got '%2' instead.")
2461 cmd.
print(QObject::tr(
"Set active S.F. index: %1").arg(sfIndex));
2463 if (cmd.
clouds().empty()) {
2465 QObject::tr(
"No point cloud loaded! (be sure to open one with "
2466 "\"-%1 [cloud filename]\" before \"-%2\")")
2470 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
2471 if (cmd.
clouds()[i].pc && cmd.
clouds()[i].pc->hasScalarFields()) {
2472 if (
static_cast<int>(
2473 cmd.
clouds()[i].pc->getNumberOfScalarFields()) >
2475 cmd.
clouds()[i].pc->setCurrentScalarField(sfIndex);
2477 cmd.
warning(QObject::tr(
"Cloud '%1' has less scalar fields "
2478 "than the index to select!")
2479 .arg(cmd.
clouds()[i].pc->getName()));
2493 for (
auto& cloudDesc : cmd.
clouds()) {
2494 if (cloudDesc.pc ) {
2495 cloudDesc.pc->deleteAllScalarFields();
2496 cloudDesc.pc->showSF(
false);
2500 for (
auto& meshDesc : cmd.
meshes()) {
2501 if (meshDesc.mesh) {
2504 static_cast<ccPointCloud*
>(cloud)->deleteAllScalarFields();
2535 return cmd.
error(QObject::tr(
"Missing parameter: SF index after %1")
2539 bool paramOk =
false;
2540 QString sfIndexStr = cmd.
arguments().takeFirst();
2541 int sfIndex = sfIndexStr.toInt(¶mOk);
2543 return cmd.
error(QObject::tr(
"Failed to read a numerical parameter: SF "
2544 "index. Got '%1' instead.")
2547 cmd.
print(QObject::tr(
"\tSF index: %1").arg(sfIndex));
2551 QObject::tr(
"Invalid SF index (positive value expected)"));
2554 for (
auto& cloudDesc : cmd.
clouds()) {
2556 if (!
removeSF(sfIndex, *cloudDesc.pc)) {
2557 cmd.
warning(QObject::tr(
"Cloud '%1' has not enough SFs")
2558 .arg(cloudDesc.pc->getName()));
2563 for (
auto& meshDesc : cmd.
meshes()) {
2564 if (meshDesc.mesh) {
2571 "Mesh '%1' vertices have not enough SFs")
2572 .arg(meshDesc.mesh->getName()));
2587 for (
auto& cloudDesc : cmd.
clouds()) {
2589 cloudDesc.pc->unallocateColors();
2590 cloudDesc.pc->showColors(
false);
2594 for (
auto& meshDesc : cmd.
meshes()) {
2595 if (meshDesc.mesh) {
2601 meshDesc.mesh->showColors(
false);
2614 for (
auto& cloudDesc : cmd.
clouds()) {
2616 cloudDesc.pc->unallocateNorms();
2617 cloudDesc.pc->showNormals(
false);
2621 for (
auto& meshDesc : cmd.
meshes()) {
2622 if (meshDesc.mesh) {
2629 static_cast<ccMesh*
>(meshDesc.mesh)->clearTriNormals();
2646 cloudDesc.pc->removeGrids();
2651 if (meshDesc.mesh) {
2667 cmd.
print(QObject::tr(
"[MATCH B.B. CENTERS]"));
2669 std::vector<CLEntityDesc*> entities;
2670 for (
auto& cloud : cmd.
clouds()) {
2671 entities.push_back(&cloud);
2673 for (
auto& mesh : cmd.
meshes()) {
2674 entities.push_back(&mesh);
2677 if (entities.empty()) {
2678 return cmd.
error(
"No entity loaded!");
2679 }
else if (entities.size() == 1) {
2680 cmd.
warning(
"Nothing to do: only one entity currently loaded!");
2685 entities.front()->getEntity()->getOwnBB().getCenter();
2686 for (
size_t i = 1; i < entities.size(); ++i) {
2687 ccHObject* ent = entities[i]->getEntity();
2697 cmd.
print(QObject::tr(
"Entity '%1' has been translated: (%2,%3,%4)")
2704 if (!errorStr.isEmpty()) {
2705 return cmd.
error(errorStr);
2718 cmd.
print(QObject::tr(
"[COMPUTE BEST FIT PLANE]"));
2721 bool makeCloudsHoriz =
false;
2722 bool keepLoaded =
false;
2725 QString argument = cmd.
arguments().front();
2731 makeCloudsHoriz =
true;
2744 if (cmd.
clouds().empty()) {
2746 QObject::tr(
"No cloud available. Be sure to open one first!"));
2749 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
2756 cmd.
print(QObject::tr(
"Plane successfully fitted: rms = %1")
2763 planeDesc.
mesh = pPlane;
2768 QString outputFilename;
2769 QString errorStr = cmd.
exportEntity(planeDesc,
"BEST_FIT_PLANE",
2771 if (!errorStr.isEmpty()) {
2776 QString txtFilename = QObject::tr(
"%1/%2_BEST_FIT_PLANE_INFO")
2777 .arg(cmd.
clouds()[i].path,
2778 cmd.
clouds()[i].basename);
2780 txtFilename += QObject::tr(
"_%1").arg(
2781 QDateTime::currentDateTime().
toString(
2782 "yyyy-MM-dd_hh'h'mm"));
2784 txtFilename += QObject::tr(
".txt");
2785 QFile txtFile(txtFilename);
2786 txtFile.open(QIODevice::WriteOnly | QIODevice::Text);
2787 QTextStream txtStream(&txtFile);
2789 txtStream << QObject::tr(
"Filename: %1").arg(outputFilename)
2791 txtStream << QObject::tr(
"Fitting RMS: %1").arg(rms)
2800 txtStream << QObject::tr(
"Normal: (%1,%2,%3)")
2801 .arg(N.
x, 0,
'f', precision)
2802 .arg(N.
y, 0,
'f', precision)
2803 .arg(N.
z, 0,
'f', precision)
2825 txtStream << makeZPosMatrix.
toString(precision,
' ')
2833 cmd.
meshes().push_back(planeDesc);
2836 if (makeCloudsHoriz) {
2839 cmd.
print(QObject::tr(
"Cloud '%1' has been transformed with "
2842 cmd.
clouds()[i].basename += QObject::tr(
"_HORIZ");
2845 if (!errorStr.isEmpty()) {
2853 "Failed to compute best fit plane for cloud '%1'")
2854 .arg(cmd.
clouds()[i].pc->getName()));
2866 cmd.
print(QObject::tr(
"[ORIENT NORMALS (MST)]"));
2871 "Missing parameter: number of neighbors after \"-%1\"")
2875 QString knnStr = cmd.
arguments().takeFirst();
2877 int knn = knnStr.toInt(&ok);
2878 if (!ok || knn <= 0) {
2880 QObject::tr(
"Invalid parameter: number of neighbors (%1)")
2884 if (cmd.
clouds().empty()) {
2886 QObject::tr(
"No cloud available. Be sure to open one first!"));
2889 QScopedPointer<ecvProgressDialog> progressDialog(
nullptr);
2892 progressDialog->setAutoClose(
false);
2895 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
2905 cmd.
clouds()[i].basename += QObject::tr(
"_NORMS_REORIENTED");
2908 if (!errorStr.isEmpty()) {
2914 QObject::tr(
"Failed to orient the normals of cloud '%1'!")
2919 if (progressDialog) {
2920 progressDialog->close();
2921 QCoreApplication::processEvents();
2932 cmd.
print(QObject::tr(
"[SOR FILTER]"));
2935 return cmd.
error(QObject::tr(
"Missing parameter: number of neighbors "
2936 "mode after \"-%1\"")
2940 QString knnStr = cmd.
arguments().takeFirst();
2942 int knn = knnStr.toInt(&ok);
2943 if (!ok || knn <= 0) {
2945 QObject::tr(
"Invalid parameter: number of neighbors (%1)")
2951 QObject::tr(
"Missing parameter: sigma multiplier after number "
2952 "of neighbors (SOR)"));
2954 QString sigmaStr = cmd.
arguments().takeFirst();
2955 double nSigma = sigmaStr.toDouble(&ok);
2956 if (!ok || nSigma < 0) {
2957 return cmd.
error(QObject::tr(
"Invalid parameter: sigma multiplier (%1)")
2961 if (cmd.
clouds().empty()) {
2963 QObject::tr(
"No cloud available. Be sure to open one first!"));
2966 QScopedPointer<ecvProgressDialog> progressDialog(
nullptr);
2969 progressDialog->setAutoClose(
false);
2972 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
2979 cloud, knn, nSigma,
nullptr, progressDialog.data());
2988 cmd.
clouds()[i].indexInFile);
2990 if (!errorStr.isEmpty()) {
2992 return cmd.
error(errorStr);
2996 delete cmd.
clouds()[i].pc;
2997 cmd.
clouds()[i].pc = cleanCloud;
2998 cmd.
clouds()[i].basename += QObject::tr(
"_SOR");
3002 return cmd.
error(QObject::tr(
"Not enough memory to create a "
3003 "clean version of cloud '%1'!")
3008 selection =
nullptr;
3011 return cmd.
error(QObject::tr(
"Failed to apply SOR filter on cloud "
3012 "'%1'! (not enough memory?)")
3017 if (progressDialog) {
3018 progressDialog->close();
3019 QCoreApplication::processEvents();
3027 QObject::tr(
"Extract vertices (as a standalone 'cloud')"),
3031 cmd.
print(QObject::tr(
"[EXTRACT VERTICES]"));
3033 if (cmd.
meshes().empty()) {
3035 QObject::tr(
"No mesh available. Be sure to open one first!"));
3039 for (
size_t i = 0; i < cmd.
meshes().
size(); ++i) {
3049 cmd.
clouds().emplace_back(
3050 pc, cmd.
meshes()[i].basename + QObject::tr(
".vertices"),
3062 if (!errorStr.isEmpty()) {
3063 return cmd.
error(errorStr);
3078 cmd.
print(QObject::tr(
"[SAMPLE POINTS ON MESH]"));
3081 return cmd.
error(QObject::tr(
"Missing parameter: sampling mode after "
3082 "\"-%1\" (POINTS/DENSITY)")
3086 bool useDensity =
false;
3087 double parameter = 0;
3089 QString sampleMode = cmd.
arguments().takeFirst().toUpper();
3090 if (sampleMode ==
"POINTS") {
3092 }
else if (sampleMode ==
"DENSITY") {
3096 QObject::tr(
"Invalid parameter: unknown sampling mode \"%1\"")
3102 QObject::tr(
"Missing parameter: value after sampling mode"));
3104 bool conversionOk =
false;
3105 parameter = cmd.
arguments().takeFirst().toDouble(&conversionOk);
3106 if (!conversionOk) {
3108 QObject::tr(
"Invalid parameter: value after sampling mode"));
3111 if (cmd.
meshes().empty()) {
3113 QObject::tr(
"No mesh available. Be sure to open one first!"));
3116 QScopedPointer<ecvProgressDialog> progressDialog(
nullptr);
3119 progressDialog->setAutoClose(
false);
3122 for (
size_t i = 0; i < cmd.
meshes().
size(); ++i) {
3124 useDensity, parameter,
true,
true,
true, progressDialog.data());
3127 return cmd.
error(QObject::tr(
"Cloud sampling failed!"));
3131 cmd.
print(QObject::tr(
"Sampled cloud created: %1 points")
3132 .arg(cloud->
size()));
3133 cmd.
clouds().emplace_back(
3135 cmd.
meshes()[i].basename + QObject::tr(
"_SAMPLED_POINTS"),
3141 if (!errorStr.isEmpty()) {
3142 return cmd.
error(errorStr);
3147 if (progressDialog) {
3148 progressDialog->close();
3149 QCoreApplication::processEvents();
3159 cmd.
print(QObject::tr(
"[CROP]"));
3162 return cmd.
error(QObject::tr(
"Missing parameter: box extents after "
3163 "\"-%1\" (Xmin:Ymin:Zmin:Xmax:Ymax:Zmax)")
3168 QObject::tr(
"No point cloud or mesh available. Be sure to open "
3169 "or generate one first!"));
3176 QString boxBlock = cmd.
arguments().takeFirst();
3177 QStringList tokens = boxBlock.split(
':');
3178 if (tokens.size() != 6) {
3180 QObject::tr(
"Invalid parameter: box extents (expected "
3181 "format is 'Xmin:Ymin:Zmin:Xmax:Ymax:Zmax')")
3185 for (
int i = 0; i < 6; ++i) {
3186 CCVector3* vec = (i < 3 ? &boxMin : &boxMax);
3192 QObject::tr(
"Invalid parameter: box extents (component "
3193 "#%1 is not a valid number)")
3202 QString argument = cmd.
arguments().front();
3212 ccBBox cropBox(boxMin, boxMax);
3215 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
3219 delete cmd.
clouds()[i].pc;
3222 cmd.
clouds()[i].basename +=
"_CROPPED";
3225 if (!errorStr.isEmpty()) {
3226 return cmd.
error(errorStr);
3231 delete cmd.
clouds()[i].pc;
3232 cmd.
clouds()[i].pc =
nullptr;
3239 for (
auto it = cmd.
clouds().begin(); it != cmd.
clouds().end();) {
3240 if (it->pc ==
nullptr) {
3241 it = cmd.
clouds().erase(it);
3250 for (
size_t i = 0; i < cmd.
meshes().size(); ++i) {
3254 delete cmd.
meshes()[i].mesh;
3256 cmd.
meshes()[i].mesh =
static_cast<ccMesh*
>(croppedMesh);
3257 cmd.
meshes()[i].basename +=
"_CROPPED";
3260 if (!errorStr.isEmpty()) {
3261 return cmd.
error(errorStr);
3266 delete cmd.
meshes()[i].mesh;
3274 for (
auto it = cmd.
meshes().begin(); it != cmd.
meshes().end();) {
3275 if (it->mesh ==
nullptr) {
3276 it = cmd.
meshes().erase(it);
3291 cmd.
print(QObject::tr(
"[COORD TO SF]"));
3295 QObject::tr(
"Missing parameter after \"-%1\" (DIMENSION)")
3298 if (cmd.
clouds().empty()) {
3300 QObject::tr(
"No point cloud available. Be sure to open or "
3301 "generate one first!"));
3305 bool exportDims[3] = {
false,
false,
false};
3306 QString dimStr = cmd.
arguments().takeFirst().toUpper();
3308 if (dimStr ==
"X") {
3309 exportDims[0] =
true;
3310 }
else if (dimStr ==
"Y") {
3311 exportDims[1] =
true;
3312 }
else if (dimStr ==
"Z") {
3313 exportDims[2] =
true;
3315 return cmd.
error(QObject::tr(
"Invalid parameter: dimension after "
3316 "\"-%1\" (expected: X, Y or Z)")
3322 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
3325 cmd.
clouds()[i].basename += QObject::tr(
"_%1_TO_SF").arg(dimStr);
3328 if (!errorStr.isEmpty()) {
3329 return cmd.
error(errorStr);
3335 "Failed to export coord. %1 to SF on cloud '%2'!")
3336 .arg(dimStr, cmd.
clouds()[i].pc->getName()));
3347 cmd.
print(QObject::tr(
"[CROP 2D]"));
3350 return cmd.
error(QObject::tr(
"Missing parameter(s) after \"-%1\" "
3351 "(ORTHO_DIM N X1 Y1 X2 Y2 ... XN YN)")
3354 if (cmd.
clouds().empty()) {
3356 QObject::tr(
"No point cloud available. Be sure to open or "
3357 "generate one first!"));
3365 unsigned char orthoDim = 2;
3367 QString orthoDimStr = cmd.
arguments().takeFirst().toUpper();
3368 if (orthoDimStr ==
"X") {
3370 }
else if (orthoDimStr ==
"Y") {
3372 }
else if (orthoDimStr ==
"Z") {
3376 QObject::tr(
"Invalid parameter: orthogonal dimension after "
3377 "\"-%1\" (expected: X, Y or Z)")
3386 QString countStr = cmd.
arguments().takeFirst();
3387 N = countStr.toUInt(&ok);
3390 QObject::tr(
"Invalid parameter: number of vertices for the "
3391 "2D polyline after \"-%1\"")
3399 return cmd.
error(QObject::tr(
"Not enough memory!"));
3402 for (
unsigned i = 0; i < N; ++i) {
3404 return cmd.
error(QObject::tr(
"Missing parameter(s): vertex #%1 "
3405 "data and following")
3411 QString coordStr = cmd.
arguments().takeFirst();
3416 "Invalid parameter: X-coordinate of vertex #%1")
3424 "Invalid parameter: Y-coordinate of vertex #%1")
3438 QString argument = cmd.
arguments().front();
3449 for (
size_t i = 0; i < cmd.
clouds().size(); ++i) {
3451 cmd.
clouds()[i].pc->crop2D(&poly, orthoDim, inside);
3453 if (ref->
size() != 0) {
3455 cmd.
clouds()[i].pc->partialClone(ref);
3460 delete cmd.
clouds()[i].pc;
3461 cmd.
clouds()[i].pc = croppedCloud;
3463 QObject::tr(
".cropped"));
3464 cmd.
clouds()[i].basename +=
"_CROPPED";
3467 if (!errorStr.isEmpty()) {
3468 return cmd.
error(errorStr);
3473 QObject::tr(
"Not enough memory to crop cloud '%1'!")
3474 .arg(cmd.
clouds()[i].pc->getName()));
3479 cmd.
warning(QObject::tr(
"No point of cloud '%1' falls inside "
3481 .arg(cmd.
clouds()[i].pc->getName()));
3485 QObject::tr(
"Crop process failed! (not enough memory)"));
3497 cmd.
print(QObject::tr(
"[COLOR BANDING]"));
3502 "Missing parameter(s) after \"-%1\" (DIM FREQUENCY)")
3506 return cmd.
error(QObject::tr(
3507 "No entity available. Be sure to open or generate one first!"));
3511 unsigned char dim = 2;
3512 QString dimStr =
"Z";
3514 dimStr = cmd.
arguments().takeFirst().toUpper();
3515 if (dimStr ==
"X") {
3517 }
else if (dimStr ==
"Y") {
3519 }
else if (dimStr ==
"Z") {
3522 return cmd.
error(QObject::tr(
"Invalid parameter: dimension after "
3523 "\"-%1\" (expected: X, Y or Z)")
3532 QString countStr = cmd.
arguments().takeFirst();
3533 freq = countStr.toDouble(&ok);
3535 return cmd.
error(QObject::tr(
"Invalid parameter: frequency after "
3536 "\"-%1 DIM\" (in Hz, integer value)")
3542 if (!cmd.
clouds().empty()) {
3543 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
3544 if (cmd.
clouds()[i].pc) {
3545 if (!cmd.
clouds()[i].pc->setRGBColorByBanding(dim, freq)) {
3546 return cmd.
error(QObject::tr(
"Not enough memory"));
3554 QObject::tr(
"COLOR_BANDING_%1_%2").arg(dimStr).arg(freq))) {
3559 if (!cmd.
meshes().empty()) {
3560 bool hasMeshes =
false;
3561 for (
size_t i = 0; i < cmd.
meshes().
size(); ++i) {
3566 return cmd.
error(QObject::tr(
"Not enough memory"));
3568 cmd.
meshes()[i].mesh->showColors(
true);
3573 "Vertices of mesh '%1' are locked (they may be "
3574 "shared by multiple entities for instance). "
3575 "Can't apply the current command on them.")
3576 .arg(cmd.
meshes()[i].mesh->getName()));
3583 QObject::tr(
"COLOR_BANDING_%1_%2").arg(dimStr).arg(freq))) {
3592 const QString&
name,
3593 const QString& keyword)
3595 m_cloud2meshDist(cloud2meshDist) {}
3598 cmd.
print(QObject::tr(
"[DISTANCE COMPUTATION]"));
3603 size_t nextMeshIndex = 0;
3604 if (cmd.
clouds().empty()) {
3609 QObject::tr(
"No point cloud available. Be sure to open or "
3610 "generate one first!"));
3613 QObject::tr(
"No point cloud available. Will use the first "
3614 "mesh vertices as compared cloud."));
3615 compEntity = &(cmd.
meshes().front());
3617 cmd.
meshes()[nextMeshIndex++].mesh->getAssociatedCloud());
3619 return cmd.
error(QObject::tr(
"Unhandled mesh vertices type"));
3626 QObject::tr(
"[C2M] Multiple point clouds loaded! Will take "
3627 "the first one by default."));
3629 compEntity = &(cmd.
clouds().front());
3630 compCloud = cmd.
clouds().front().pc;
3632 assert(compEntity && compCloud);
3637 if (cmd.
meshes().size() <= nextMeshIndex) {
3638 return cmd.
error(QObject::tr(
3639 "No mesh available. Be sure to open one first!"));
3640 }
else if (cmd.
meshes().size() != nextMeshIndex + 1) {
3641 cmd.
warning(QString(
"Multiple meshes loaded! We take the %1 one by "
3643 .arg(nextMeshIndex == 0 ?
"first" :
"second"));
3645 refEntity = cmd.
meshes()[nextMeshIndex].mesh;
3647 if (cmd.
clouds().size() < 2) {
3648 return cmd.
error(QObject::tr(
3649 "Only one point cloud available. Be sure to open or "
3650 "generate a second one before performing C2C distance!"));
3651 }
else if (cmd.
clouds().size() > 2) {
3653 QObject::tr(
"More than 3 point clouds loaded! We take the "
3654 "second one as reference by default"));
3656 refEntity = cmd.
clouds()[1].pc;
3660 bool flipNormals =
false;
3661 double maxDist = 0.0;
3663 int maxThreadCount = 0;
3665 bool splitXYZ =
false;
3671 QString argument = cmd.
arguments().front();
3681 "Parameter \"-%1\" ignored: only for C2M distance!"));
3690 QObject::tr(
"Missing parameter: value after \"-%1\"")
3693 bool conversionOk =
false;
3694 maxDist = cmd.
arguments().takeFirst().toDouble(&conversionOk);
3695 if (!conversionOk) {
3697 QObject::tr(
"Invalid parameter: value after \"-%1\"")
3707 QObject::tr(
"Missing parameter: value after \"-%1\"")
3710 bool conversionOk =
false;
3712 if (!conversionOk) {
3714 QObject::tr(
"Invalid parameter: value after \"-%1\"")
3726 "Parameter \"-%1\" ignored: only for C2C distance!"));
3734 QString modelType = cmd.
arguments().takeFirst().toUpper();
3735 if (modelType ==
"LS") {
3737 }
else if (modelType ==
"TRI") {
3739 }
else if (modelType ==
"HF") {
3742 return cmd.
error(QObject::tr(
"Invalid parameter: unknown "
3743 "model type \"%1\"")
3747 return cmd.
error(QObject::tr(
"Missing parameter: model type "
3748 "after \"-%1\" (LS/TRI/HF)")
3753 QString nType = cmd.
arguments().takeFirst().toUpper();
3754 if (nType ==
"KNN") {
3756 }
else if (nType ==
"SPHERE") {
3759 return cmd.
error(QObject::tr(
"Invalid parameter: unknown "
3760 "neighborhood type \"%1\"")
3765 QObject::tr(
"Missing parameter: expected neighborhood "
3766 "type after model type (KNN/SPHERE)"));
3771 bool conversionOk =
false;
3772 nSize = cmd.
arguments().takeFirst().toDouble(&conversionOk);
3773 if (!conversionOk) {
3774 return cmd.
error(QObject::tr(
3775 "Invalid parameter: neighborhood size"));
3778 return cmd.
error(QObject::tr(
3779 "Missing parameter: expected neighborhood size after "
3780 "neighborhood type (neighbor count/sphere radius)"));
3788 return cmd.
error(QObject::tr(
"Missing parameter: max thread "
3794 maxThreadCount = cmd.
arguments().takeFirst().toInt(&ok);
3795 if (!ok || maxThreadCount < 0) {
3796 return cmd.
error(QObject::tr(
"Invalid thread count! (after %1)")
3812 return cmd.
error(QObject::tr(
"Failed to initialize comparison dialog"));
3817 compDlg.maxDistCheckBox->setChecked(
true);
3818 compDlg.maxSearchDistSpinBox->setValue(maxDist);
3821 compDlg.octreeLevelComboBox->setCurrentIndex(
octreeLevel);
3823 if (maxThreadCount != 0) {
3824 compDlg.maxThreadCountSpinBox->setValue(maxThreadCount);
3830 compDlg.flipNormalsCheckBox->setChecked(
true);
3840 compDlg.split3DCheckBox->setChecked(
true);
3842 if (modelIndex != 0) {
3843 compDlg.localModelComboBox->setCurrentIndex(modelIndex);
3845 compDlg.lmKNNRadioButton->setChecked(
true);
3846 compDlg.lmKNNSpinBox->setValue(
static_cast<int>(nSize));
3848 compDlg.lmRadiusRadioButton->setChecked(
true);
3849 compDlg.lmRadiusDoubleSpinBox->setValue(nSize);
3857 QObject::tr(
"An error occurred during distances computation!"));
3864 suffix += QObject::tr(
"_MAX_DIST_%1").arg(maxDist);
3871 if (!errorStr.isEmpty()) {
3872 return cmd.
error(errorStr);
3890 cmd.
print(QObject::tr(
"[CLOSEST POINT SET]"));
3892 if (cmd.
clouds().size() < 2) {
3894 QObject::tr(
"At least two point clouds are needed to compute "
3895 "the closest point set!"));
3896 }
else if (cmd.
clouds().size() > 2) {
3898 QObject::tr(
"More than 3 point clouds loaded! We take the "
3899 "second one as reference by default"));
3908 assert(compPointCloud && refPointCloud);
3914 params.CPSet = &closestPointSet;
3920 compPointCloud, refPointCloud,
params, &pDlg);
3932 cloudDesc, QString(),
nullptr,
3934 if (!errorStr.isEmpty()) {
3935 cmd.
error(errorStr);
3938 cmd.
clouds().push_back(cloudDesc);
3949 cmd.
print(QObject::tr(
"[STATISTICAL TEST]"));
3955 return cmd.
error(QObject::tr(
"Missing parameter: distribution type "
3956 "after \"-%1\" (GAUSS/WEIBULL)")
3960 QString distribStr = cmd.
arguments().takeFirst().toUpper();
3961 if (distribStr ==
"GAUSS") {
3964 return cmd.
error(QObject::tr(
3965 "Missing parameter: mean value after \"GAUSS\""));
3967 bool conversionOk =
false;
3968 double mu = cmd.
arguments().takeFirst().toDouble(&conversionOk);
3969 if (!conversionOk) {
3970 return cmd.
error(QObject::tr(
3971 "Invalid parameter: mean value after \"GAUSS\""));
3975 return cmd.
error(QObject::tr(
3976 "Missing parameter: sigma value after \"GAUSS\" {mu}"));
3978 conversionOk =
false;
3979 double sigma = cmd.
arguments().takeFirst().toDouble(&conversionOk);
3980 if (!conversionOk) {
3981 return cmd.
error(QObject::tr(
3982 "Invalid parameter: sigma value after \"GAUSS\" {mu}"));
3988 static_cast<ScalarType
>(mu),
3989 static_cast<ScalarType
>(sigma *
3993 }
else if (distribStr ==
"WEIBULL") {
3996 return cmd.
error(QObject::tr(
3997 "Missing parameter: a value after \"WEIBULL\""));
3999 bool conversionOk =
false;
4000 double a = cmd.
arguments().takeFirst().toDouble(&conversionOk);
4001 if (!conversionOk) {
4002 return cmd.
error(QObject::tr(
4003 "Invalid parameter: a value after \"WEIBULL\""));
4007 return cmd.
error(QObject::tr(
4008 "Missing parameter: b value after \"WEIBULL\" {a}"));
4010 conversionOk =
false;
4011 double b = cmd.
arguments().takeFirst().toDouble(&conversionOk);
4012 if (!conversionOk) {
4013 return cmd.
error(QObject::tr(
4014 "Invalid parameter: b value after \"WEIBULL\" {a}"));
4019 QObject::tr(
"Missing parameter: shift value after "
4020 "\"WEIBULL\" {a} {b}"));
4022 conversionOk =
false;
4023 double shift = cmd.
arguments().takeFirst().toDouble(&conversionOk);
4024 if (!conversionOk) {
4026 QObject::tr(
"Invalid parameter: shift value after "
4027 "\"WEIBULL\" {a} {b}"));
4033 static_cast<ScalarType
>(b),
4034 static_cast<ScalarType
>(shift));
4039 "Invalid parameter: unknown distribution \"%1\"")
4045 double pValue = 0.0005;
4048 return cmd.
error(QObject::tr(
4049 "Missing parameter: p-value after distribution"));
4051 bool conversionOk =
false;
4052 pValue = cmd.
arguments().takeFirst().toDouble(&conversionOk);
4053 if (!conversionOk) {
4054 return cmd.
error(QObject::tr(
4055 "Invalid parameter: p-value after distribution"));
4064 QObject::tr(
"Missing parameter: neighbors after p-value"));
4066 bool conversionOk =
false;
4067 kNN = cmd.
arguments().takeFirst().toUInt(&conversionOk);
4068 if (!conversionOk) {
4070 QObject::tr(
"Invalid parameter: neighbors after p-value"));
4074 if (cmd.
clouds().empty()) {
4076 QObject::tr(
"No cloud available. Be sure to open one first!"));
4079 QScopedPointer<ecvProgressDialog> progressDialog(
nullptr);
4082 progressDialog->setAutoClose(
false);
4085 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
4091 assert(outSF->capacity() != 0);
4097 if (chi2SfIdx < 0) {
4101 if (chi2SfIdx < 0) {
4103 return cmd.
error(QObject::tr(
4104 "Couldn't allocate a new scalar field for computing "
4105 "chi2 distances! Try to free some memory ..."));
4115 cmd.
error(QObject::tr(
4116 "Couldn't compute octree for cloud '%1'!")
4124 progressDialog.data(),
4127 cmd.
print(QObject::tr(
"[Chi2 Test] %1 test result = %2")
4138 chi2dist *= chi2dist;
4147 cmd.
clouds()[i].basename +=
4148 QObject::tr(
"_STAT_TEST_%1").arg(distrib->
getName());
4151 if (!errorStr.isEmpty()) {
4152 return cmd.
error(errorStr);
4158 if (progressDialog) {
4159 progressDialog->close();
4160 QCoreApplication::processEvents();
4171 cmd.
print(QObject::tr(
"[DELAUNAY TRIANGULATION]"));
4173 bool axisAligned =
true;
4174 double maxEdgeLength = 0;
4177 QString argument = cmd.
arguments().front();
4186 axisAligned =
false;
4194 QObject::tr(
"Missing parameter: max edge length value "
4199 maxEdgeLength = cmd.
arguments().takeFirst().toDouble(&ok);
4203 "Invalid value for max edge length! (after %1)")
4206 QObject::tr(
"Max edge length: %1").arg(maxEdgeLength));
4213 cmd.
print(QObject::tr(
"Axis aligned: %1").arg(axisAligned ?
"yes" :
"no"));
4216 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
4218 cmd.
print(QObject::tr(
"\tProcessing cloud #%1 (%2)")
4232 cmd.
print(QObject::tr(
"\tResulting mesh: #%1 faces, %2 vertices")
4237 meshDesc.
mesh = mesh;
4244 QString outputFilename;
4246 cmd.
exportEntity(meshDesc,
"DELAUNAY", &outputFilename);
4247 if (!errorStr.isEmpty()) {
4253 cmd.
meshes().push_back(meshDesc);
4274 cmd.
print(QObject::tr(
"[SF ARITHMETIC]"));
4277 return cmd.
error(QObject::tr(
"Missing parameter(s): SF index and/or "
4278 "operation after '%1' (2 values expected)")
4285 QString sfIndexStr = cmd.
arguments().takeFirst();
4289 sfIndex = sfIndexStr.toInt(&ok);
4291 if (!ok || sfIndex < 0) {
4292 return cmd.
error(QObject::tr(
"Invalid SF index! (after %1)")
4300 QString opName = cmd.
arguments().takeFirst();
4304 QObject::tr(
"Unknown operation! (%1)").arg(opName));
4307 QObject::tr(
"Operation %1 can't be applied with %2")
4313 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
4326 QObject::tr(
"Failed top apply operation on cloud '%1'")
4331 if (!errorStr.isEmpty()) {
4332 return cmd.
error(errorStr);
4339 for (
size_t j = 0; j < cmd.
meshes().
size(); ++j) {
4340 bool isLocked =
false;
4354 QObject::tr(
"Failed top apply operation on mesh '%1'")
4359 if (!errorStr.isEmpty()) {
4360 return cmd.
error(errorStr);
4374 cmd.
print(QObject::tr(
"[SF OPERATION]"));
4379 "Missing parameter(s): SF index and/or operation "
4380 "and/or scalar value after '%1' (3 values expected)")
4387 QString sfIndexStr = cmd.
arguments().takeFirst();
4391 sfIndex = sfIndexStr.toInt(&ok);
4394 if (!ok || sfIndex < 0) {
4396 QObject::tr(
"Invalid SF index! (after %1)").arg(
COMMAND_SF_OP));
4403 QString opName = cmd.
arguments().takeFirst();
4407 QObject::tr(
"Unknown operation! (%1)").arg(opName));
4410 QObject::tr(
"Operation %1 can't be applied with %2")
4419 value = cmd.
arguments().takeFirst().toDouble(&ok);
4421 return cmd.
error(QObject::tr(
"Invalid scalar value! (after %1)")
4433 for (
size_t i = 0; i < cmd.
clouds().
size(); ++i) {
4446 QObject::tr(
"Failed top apply operation on cloud '%1'")
4450 if (!errorStr.isEmpty()) {
4451 return cmd.
error(errorStr);
4458 for (
size_t j = 0; j < cmd.
meshes().
size(); ++j) {
4459 bool isLocked =
false;
4473 QObject::tr(
"Failed top apply operation on mesh '%1'")
4477 if (!errorStr.isEmpty()) {
4478 return cmd.
error(errorStr);
4492 cmd.
print(QObject::tr(
"[RENAME SF]"));
4496 QObject::tr(
"Missing parameter(s): SF index and/or scalar "
4497 "field name after '%1' (2 values expected)")
4504 QString sfIndexStr = cmd.
arguments().takeFirst();
4508 sfIndex = sfIndexStr.toInt(&ok);
4511 if (!ok || sfIndex == -1) {
4513 QObject::tr(
"Invalid SF index! (after %1)").arg(
COMMAND_SF_OP));
4517 QString sfName = cmd.
arguments().takeFirst();
4525 (sfIndex < 0 ? static_cast<int>(
4529 int indexOfSFWithSameName =
4531 if (indexOfSFWithSameName >= 0 &&
4532 thisSFIndex != indexOfSFWithSameName) {
4534 "A SF with the same name is already defined on cloud " +
4540 return cmd.
error(
"Internal error: invalid SF index");
4542 sf->
setName(qPrintable(sfName));
4545 QString errorStr = cmd.
exportEntity(cloudDesc,
"SF_RENAMED");
4546 if (!errorStr.isEmpty()) {
4547 return cmd.
error(errorStr);
4555 bool isLocked =
false;
4561 (sfIndex < 0 ? static_cast<int>(
4565 int indexOfSFWithSameName =
4567 if (indexOfSFWithSameName >= 0 &&
4568 thisSFIndex != indexOfSFWithSameName) {
4570 "A SF with the same name is already defined on cloud " +
4576 return cmd.
error(
"Internal error: invalid SF index");
4578 sf->
setName(qPrintable(sfName));
4581 QString errorStr = cmd.
exportEntity(meshDesc,
"SF_RENAMED");
4582 if (!errorStr.isEmpty()) {
4583 return cmd.
error(errorStr);
4596 cmd.
print(QObject::tr(
"[ICP]"));
4599 bool referenceIsFirst =
false;
4600 bool adjustScale =
false;
4601 bool enableFarthestPointRemoval =
false;
4602 double minErrorDiff = 1.0e-6;
4603 unsigned iterationCount = 0;
4604 unsigned randomSamplingLimit = 20000;
4605 unsigned overlap = 100;
4606 int modelSFAsWeights = -1;
4607 int dataSFAsWeights = -1;
4608 int maxThreadCount = 0;
4609 int transformationFilters = 0;
4612 QString argument = cmd.
arguments().front();
4618 referenceIsFirst =
true;
4630 enableFarthestPointRemoval =
true;
4637 return cmd.
error(QObject::tr(
"Missing parameter: min error "
4638 "difference after '%1'")
4642 minErrorDiff = cmd.
arguments().takeFirst().toDouble(&ok);
4643 if (!ok || minErrorDiff <= 0) {
4644 return cmd.
error(QObject::tr(
"Invalid value for min. error "
4645 "difference! (after %1)")
4654 return cmd.
error(QObject::tr(
"Missing parameter: number of "
4655 "iterations after '%1'")
4659 QString arg = cmd.
arguments().takeFirst();
4660 iterationCount = arg.toUInt(&ok);
4661 if (!ok || iterationCount == 0)
4663 QObject::tr(
"Invalid number of iterations! (%1)")
4671 return cmd.
error(QObject::tr(
"Missing parameter: overlap "
4672 "percentage after '%1'")
4676 QString arg = cmd.
arguments().takeFirst();
4677 overlap = arg.toUInt(&ok);
4678 if (!ok || overlap < 10 || overlap > 100) {
4679 return cmd.
error(QObject::tr(
"Invalid overlap value! (%1 --> "
4680 "should be between 10 and 100)")
4690 QObject::tr(
"Missing parameter: random sampling limit "
4695 randomSamplingLimit = cmd.
arguments().takeFirst().toUInt(&ok);
4696 if (!ok || randomSamplingLimit < 3) {
4698 QObject::tr(
"Invalid random sampling limit! (after %1)")
4708 QObject::tr(
"Missing parameter: SF index after '%1'")
4711 QString sfIndex = cmd.
arguments().takeFirst();
4713 modelSFAsWeights = -2;
4716 modelSFAsWeights = sfIndex.toInt(&ok);
4717 if (!ok || modelSFAsWeights < 0) {
4719 QObject::tr(
"Invalid SF index! (after %1)")
4730 QObject::tr(
"Missing parameter: SF index after '%1'")
4733 QString sfIndex = cmd.
arguments().takeFirst();
4735 dataSFAsWeights = -2;
4738 dataSFAsWeights = sfIndex.toInt(&ok);
4739 if (!ok || dataSFAsWeights < 0) {
4741 QObject::tr(
"Invalid SF index! (after %1)")
4751 return cmd.
error(QObject::tr(
"Missing parameter: max thread "
4757 maxThreadCount = cmd.
arguments().takeFirst().toInt(&ok);
4758 if (!ok || maxThreadCount < 0) {
4759 return cmd.
error(QObject::tr(
"Invalid thread count! (after %1)")
4768 QString rotation = cmd.
arguments().takeFirst().toUpper();
4769 if (rotation ==
"XYZ") {
4770 transformationFilters = 0;
4771 }
else if (rotation ==
"X") {
4772 transformationFilters = 2;
4773 }
else if (rotation ==
"Y") {
4774 transformationFilters = 4;
4775 }
else if (rotation ==
"Z") {
4776 transformationFilters = 1;
4777 }
else if (rotation ==
"NONE") {
4778 transformationFilters = 7;
4780 return cmd.
error(QObject::tr(
"Invalid parameter: unknown "
4781 "rotation filter \"%1\"")
4786 QObject::tr(
"Missing parameter: rotation filter after "
4787 "\"-%1\" (XYZ/X/Y/Z/NONE)")
4800 if (!cmd.
clouds().empty()) {
4801 dataAndModel[index++] = &cmd.
clouds()[0];
4802 if (cmd.
clouds().size() > 1) {
4803 dataAndModel[index++] = &cmd.
clouds()[1];
4806 if (index < 2 && !cmd.
meshes().empty()) {
4807 dataAndModel[index++] = &cmd.
meshes()[0];
4808 if (index < 2 && cmd.
meshes().size() > 1) {
4809 dataAndModel[index++] = &cmd.
meshes()[1];
4814 return cmd.
error(QObject::tr(
4815 "Not enough loaded entities (expect at least 2!)"));
4820 if (referenceIsFirst) {
4821 std::swap(dataAndModel[0], dataAndModel[1]);
4825 if (dataSFAsWeights != -1) {
4832 QObject::tr(
"Invalid SF index for data entity! (%1)")
4833 .arg(dataSFAsWeights));
4835 if (dataSFAsWeights < 0)
4840 cmd.
print(QObject::tr(
"[ICP] SF #%1 (data entity) will be used as "
4842 .arg(dataSFAsWeights));
4847 if (modelSFAsWeights != -1) {
4854 QObject::tr(
"Invalid SF index for model entity! (%1)")
4855 .arg(modelSFAsWeights));
4857 if (modelSFAsWeights < 0)
4862 cmd.
print(QObject::tr(
"[ICP] SF #%1 (model entity) will be used as "
4864 .arg(modelSFAsWeights));
4870 double finalError = 0.0;
4871 double finalScale = 1.0;
4872 unsigned finalPointCount = 0;
4896 finalScale, finalError, finalPointCount,
4897 parameters, dataSFAsWeights >= 0,
4901 cmd.
print(QObject::tr(
"Entity '%1' has been registered")
4903 cmd.
print(QObject::tr(
"RMS: %1").arg(finalError));
4904 cmd.
print(QObject::tr(
"Number of points used for final step: %1")
4905 .arg(finalPointCount));
4909 QString txtFilename = QObject::tr(
"%1/%2_REGISTRATION_MATRIX")
4910 .arg(dataAndModel[0]->
path,
4911 dataAndModel[0]->basename);
4913 txtFilename += QObject::tr(
"_%1").arg(
4914 QDateTime::currentDateTime().
toString(
4915 "yyyy-MM-dd_hh'h'mm"));
4916 txtFilename += QObject::tr(
".txt");
4917 QFile txtFile(txtFilename);
4918 txtFile.open(QIODevice::WriteOnly | QIODevice::Text);
4919 QTextStream txtStream(&txtFile);
4925 dataAndModel[0]->
basename += QObject::tr(
"_REGISTERED");
4928 if (!errorStr.isEmpty()) {
4929 return cmd.
error(errorStr);
4945 return cmd.
error(QObject::tr(
"Missing parameter: format (ASCII, "
4946 "BINARY_LE, or BINARY_BE) after '%1'")
4954 QString plyFormat = cmd.
arguments().takeFirst().toUpper();
4957 if (plyFormat ==
"ASCII") {
4959 }
else if (plyFormat ==
"BINARY_BE") {
4961 }
else if (plyFormat ==
"BINARY_LE") {
4965 QObject::tr(
"Invalid PLY format! ('%1')").arg(plyFormat));
4973 QObject::tr(
"Compute structured cloud normals"),
4987 QStringList& fileNames) {
4992 auto argument = cmd.
arguments().takeFirst();
4993 while (!argument.isEmpty()) {
4994 auto firstChar = argument.at(0);
4995 if (firstChar ==
'\'' || firstChar ==
'\"') {
4996 auto end = argument.indexOf(firstChar, 1);
4998 return cmd.
error(QObject::tr(
"A file starting with %1 does not "
4999 "have a closing %1")
5003 fileNames.push_back(argument.mid(1, end - 1));
5004 argument.remove(0, end + 1);
5005 if (argument.startsWith(
' ')) {
5006 argument.remove(0, 1);
5009 auto end = argument.indexOf(
' ');
5011 end = argument.length();
5013 fileNames.push_back(argument.left(end));
5014 argument.remove(0, end + 1);
5021 QFileInfo fInfo(fileName);
5023 desc.
path = fInfo.filePath().left(fInfo.filePath().length() -
5024 fInfo.fileName().length());
5031 bool allAtOnce =
false;
5032 bool setFileNames =
false;
5033 QStringList fileNames;
5037 QString argument = cmd.
arguments().front();
5045 setFileNames =
true;
5055 if (setFileNames && allAtOnce && fileNames.size() != 1) {
5056 return cmd.
error(QObject::tr(
"Invalid parameter: specified %1 file "
5057 "names, but ALL_AT_ONCE is on")
5058 .arg(fileNames.size()));
5060 if (setFileNames && !allAtOnce && fileNames.size() != cmd.
clouds().size()) {
5061 return cmd.
error(QObject::tr(
"Invalid parameter: specified %1 file "
5062 "names, but there are %2 clouds")
5063 .arg(fileNames.size())
5064 .arg(cmd.
clouds().size()));
5074 for (
int i = 0; i < fileNames.size(); ++i) {
5080 auto res = cmd.
saveClouds(QString(), allAtOnce,
5081 setFileNames ? &fileNames[0] :
nullptr);
5095 bool allAtOnce =
false;
5096 bool setFileNames =
false;
5097 QStringList fileNames;
5101 QString argument = cmd.
arguments().front();
5109 setFileNames =
true;
5119 if (setFileNames && allAtOnce && fileNames.size() != 1) {
5120 return cmd.
error(QObject::tr(
"Invalid parameter: specified %1 file "
5121 "names, but ALL_AT_ONCE is on")
5122 .arg(fileNames.size()));
5124 if (setFileNames && !allAtOnce && fileNames.size() != cmd.
meshes().size()) {
5125 return cmd.
error(QObject::tr(
"Invalid parameter: specified %1 file "
5126 "names, but there are %2 meshes")
5127 .arg(fileNames.size())
5128 .arg(cmd.
meshes().size()));
5138 for (
int i = 0; i < fileNames.size(); ++i) {
5144 auto res = cmd.
saveMeshes(QString(), allAtOnce,
5145 setFileNames ? &fileNames[0] :
nullptr);
5162 QObject::tr(
"Missing parameter: option after '%1' (%2/%3)")
5166 QString option = cmd.
arguments().takeFirst().toUpper();
5168 cmd.
print(QObject::tr(
"Auto-save is enabled"));
5171 cmd.
print(QObject::tr(
"Auto-save is disabled"));
5176 "Unrecognized option after '%1' (%2 or %3 expected)")
5189 return cmd.
error(QObject::tr(
"Missing parameter: filename after '%1'")
5262 return cmd.
error(QObject::tr(
"Missing parameter: kernel size after %1")
5266 bool paramOk =
false;
5267 QString kernelStr = cmd.
arguments().takeFirst();
5271 return cmd.
error(QObject::tr(
"Failed to read a numerical parameter: "
5272 "kernel size. Got '%1' instead.")
5275 cmd.
print(QObject::tr(
"\tKernel size: %1").arg(kernelSize));
5277 if (cmd.
clouds().empty()) {
5278 return cmd.
error(QObject::tr(
"No point cloud on which to compute first "
5279 "order moment! (be sure to open one with "
5280 "\"-%1 [cloud filename]\" before \"-%2\")")
5286 entities.resize(cmd.
clouds().size());
5287 for (
size_t i = 0; i < cmd.
clouds().size(); ++i) {
5288 entities[i] = cmd.
clouds()[i].pc;
5296 !cmd.
saveClouds(QObject::tr(
"MOMENT_KERNEL_%2").arg(kernelSize))) {
5308 cmd.
print(QObject::tr(
"[FEATURE]"));
5312 QObject::tr(
"Missing parameter: feature type after \"-%1\"")
5316 QString featureTypeStr = cmd.
arguments().takeFirst().toUpper();
5319 if (featureTypeStr ==
"SUM_OF_EIGENVALUES") {
5321 }
else if (featureTypeStr ==
"OMNIVARIANCE") {
5323 }
else if (featureTypeStr ==
"EIGENTROPY") {
5325 }
else if (featureTypeStr ==
"ANISOTROPY") {
5327 }
else if (featureTypeStr ==
"PLANARITY") {
5329 }
else if (featureTypeStr ==
"LINEARITY") {
5331 }
else if (featureTypeStr ==
"PCA1") {
5333 }
else if (featureTypeStr ==
"PCA2") {
5335 }
else if (featureTypeStr ==
"SURFACE_VARIATION") {
5337 }
else if (featureTypeStr ==
"SPHERICITY") {
5339 }
else if (featureTypeStr ==
"VERTICALITY") {
5341 }
else if (featureTypeStr ==
"EIGENVALUE1") {
5343 }
else if (featureTypeStr ==
"EIGENVALUE2") {
5345 }
else if (featureTypeStr ==
"EIGENVALUE3") {
5350 "Invalid feature type after \"-%1\". Got '%2' instead of:\n\
5351 - SUM_OF_EIGENVALUES\n\
5359 - SURFACE_VARIATION\n\
5369 return cmd.
error(QObject::tr(
5370 "Missing parameter: kernel size after feature type"));
5373 bool paramOk =
false;
5374 QString kernelStr = cmd.
arguments().takeFirst();
5378 return cmd.
error(QObject::tr(
"Failed to read a numerical parameter: "
5379 "kernel size. Got '%1' instead.")
5382 cmd.
print(QObject::tr(
"\tKernel size: %1").arg(kernelSize));
5384 if (cmd.
clouds().empty()) {
5385 return cmd.
error(QObject::tr(
"No point cloud on which to compute "
5386 "feature! (be sure to open one with \"-%1 "
5387 "[cloud filename]\" before \"-%2\")")
5393 entities.resize(cmd.
clouds().size());
5394 for (
size_t i = 0; i < cmd.
clouds().size(); ++i) {
5395 entities[i] = cmd.
clouds()[i].pc;
5403 !cmd.
saveClouds(QObject::tr(
"%1_FEATURE_KERNEL_%2")
5404 .arg(featureTypeStr)
5405 .arg(kernelSize))) {
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
float PointCoordinateType
Type of the coordinates of a (N-D) point.
cmdLineReadable * params[]
static void SaveColumnsNamesHeader(bool state)
static void SavePointCountHeader(bool state)
static void SaveSFBeforeColor(bool state)
Sets whether color and SF should be swapped (default is color then SF)
static void SetOutputSFPrecision(int prec)
Sets the default output scalar values precision (as saving time)
static QString GetFileFilter()
static void SetOutputSeparatorIndex(int separatorIndex)
Sets the default output separator (as saving time)
static void SetOutputCoordsPrecision(int prec)
Sets the default output coords precision (as saving time)
static void SetDefaultSkippedLineCount(int count)
Sets the default number of skipped lines (at loading time)
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
static CVLog * TheInstance()
Returns the static and unique instance.
static bool Error(const char *format,...)
Display an error dialog with formatted message.
static const FilterContainer & GetFilters()
Returns the set of all registered filters.
std::vector< FileIOFilter::Shared > FilterContainer
Type of a I/O filters container.
static void SetDefaultOutputFormat(e_ply_storage_mode format)
static Shared LoadFromXML(QString filename)
Loads a color scale from an XML file.
QSharedPointer< ccColorScale > Shared
Shared pointer type.
virtual QString hierarchyExportExt() const =0
bool autoSaveMode() const
void toggleAutoSaveMode(bool state)
void toggleAddTimestamp(bool state)
bool addTimestamp() const
virtual QString cloudExportExt() const =0
Returns the current cloud(s) export extension (warning: can be anything)
virtual QStringList & arguments()=0
Returns the list of arguments.
virtual bool saveClouds(QString suffix=QString(), bool allAtOnce=false, const QString *allAtOnceFileName=nullptr)=0
Saves all clouds.
virtual void warning(const QString &message) const =0
virtual QString hierarchyExportFormat() const =0
Returns the current hierarchy(ies) export format.
bool nextCommandIsGlobalShift() const
Returns whether the nex command is the '-GLOBAL_SHIFT' option.
bool processGlobalShiftCommand(GlobalShiftOptions &options)
virtual void print(const QString &message) const =0
virtual QString meshExportExt() const =0
Returns the current mesh(es) export extension (warning: can be anything)
virtual bool error(const QString &message) const =0
virtual QString meshExportFormat() const =0
Returns the current mesh(es) export format.
static bool IsCommand(const QString &token, const char *command)
Test whether a command line token is a valid command keyword or not.
virtual QString cloudExportFormat() const =0
Returns the current cloud(s) export format.
virtual bool importFile(QString filename, const GlobalShiftOptions &globalShiftOptions, FileIOFilter::Shared filter=FileIOFilter::Shared(nullptr))=0
Loads a file with a specific filter.
virtual void setCloudExportFormat(QString format, QString ext)=0
Sets the current cloud(s) export format and extension.
bool silentMode() const
Returns the silent mode.
virtual QString exportEntity(CLEntityDesc &entityDesc, const QString &suffix=QString(), QString *outputFilename=nullptr, ccCommandLineInterface::ExportOptions options=ExportOption::NoOptions)=0
Exports a cloud or a mesh.
virtual std::vector< CLMeshDesc > & meshes()
Currently opened meshes and their filename.
virtual ecvProgressDialog * progressDialog()
Returns a (shared) progress dialog (if any is available)
virtual void removeClouds(bool onlyLast=false)=0
Removes all clouds (or only the last one ;)
virtual CLLoadParameters & fileLoadingParams()
File loading parameters.
virtual void removeMeshes(bool onlyLast=false)=0
Removes all meshes (or only the last one ;)
virtual bool saveMeshes(QString suffix=QString(), bool allAtOnce=false, const QString *allAtOnceFileName=nullptr)=0
Saves all meshes.
int numericalPrecision() const
Returns the numerical precision.
virtual void setHierarchyExportFormat(QString format, QString ext)=0
Sets the current hierarchy(ies) export format and extension.
virtual QDialog * widgetParent()
Returns a (widget) parent (if any is available)
virtual void setMeshExportFormat(QString format, QString ext)=0
Sets the current mesh(es) export format and extension.
virtual std::vector< CLCloudDesc > & clouds()
Currently opened point clouds and their filename.
Dialog for cloud/cloud or cloud/mesh comparison setting.
bool initDialog()
Should be called once after the dialog is created.
virtual void showNormals(bool state)
Sets normals visibility.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
QString toString(int precision=12, QChar separator=' ') const
Returns matrix as a string.
void applyRotation(Vector3Tpl< float > &vec) const
Applies rotation only to a 3D vector (in place) - float version.
static ccGLMatrixTpl< float > FromToRotation(const Vector3Tpl< float > &from, const Vector3Tpl< float > &to)
Creates a transformation matrix that rotates a vector to another.
virtual bool fromAsciiFile(QString filename)
Loads matrix from an ASCII file.
void setTranslation(const Vector3Tpl< float > &Tr)
Sets translation (float version)
Float version of ccGLMatrixTpl.
void showNormals(bool state) override
Sets normals visibility.
virtual ccGenericPointCloud * getAssociatedCloud() const =0
Returns the vertices cloud.
A 3D cloud interface with associated features (color, normals, octree, etc.)
virtual ccOctree::Shared computeOctree(cloudViewer::GenericProgressCallback *progressCb=nullptr, bool autoAddChild=true)
Computes the cloud octree.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
static ccMesh * ToMesh(ccHObject *obj)
Converts current object to ccMesh (if possible)
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
static ccShiftedObject * ToShifted(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccShiftedObject.
Hierarchical CLOUDVIEWER Object.
virtual ccBBox getOwnBB(bool withGLFeatures=false)
Returns the entity's own bounding-box.
void detachChild(ccHObject *child)
Detaches a specific child.
void applyGLTransformation_recursive(const ccGLMatrix *trans=nullptr)
Applies the active OpenGL transformation to the entity (recursive)
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
ccHObject * getParent() const
Returns parent object.
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
static ccMesh * Triangulate(ccGenericPointCloud *cloud, cloudViewer::TRIANGULATION_TYPES type, bool updateNormals=false, PointCoordinateType maxEdgeLength=0, unsigned char dim=2)
Creates a Delaunay 2.5D mesh from a point cloud.
void clearTriNormals()
Removes per-triangle normals.
ccGenericPointCloud * getAssociatedCloud() const override
Returns the vertices cloud.
bool hasNormals() const override
Returns whether normals are enabled or not.
virtual unsigned size() const override
Returns the number of triangles.
void invertNormals()
Inverts normals (if any)
static PointCoordinateType GuessBestRadius(ccGenericPointCloud *cloud, cloudViewer::DgmOctree *cloudOctree=nullptr, cloudViewer::GenericProgressCallback *progressCb=nullptr)
static QString ConvertDipAndDipDirToString(PointCoordinateType dip_deg, PointCoordinateType dipDir_deg)
Converts geological 'dip direction & dip' parameters to a string.
static void ConvertNormalToDipAndDipDir(const CCVector3 &N, PointCoordinateType &dip_deg, PointCoordinateType &dipDir_deg)
Converts a normal vector to geological 'dip direction & dip' parameters.
Orientation
'Default' orientations
virtual QString getName() const
Returns object name.
bool isA(CV_CLASS_ENUM type) const
virtual void setName(const QString &name)
Sets object name.
virtual void setEnabled(bool state)
Sets the "enabled" property.
QSharedPointer< ccOctree > Shared
Shared pointer.
static ccPlane * Fit(cloudViewer::GenericIndexedCloudPersist *cloud, double *rms=0)
Fits a plane primitive on a cloud.
CCVector3 getNormal() const override
Returns the entity normal.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
bool setRGBColorByBanding(unsigned char dim, double freq)
Assigns color to points by 'banding'.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool hasNormals() const override
Returns whether normals are enabled or not.
bool computeNormalsWithOctree(CV_LOCAL_MODEL_TYPES model, ccNormalVectors::Orientation preferredOrientation, PointCoordinateType defaultRadius, ecvProgressDialog *pDlg=nullptr)
Compute the normals by approximating the local surface around each point.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool exportCoordToSF(bool exportDims[3])
Exports the specified coordinate dimension(s) to scalar field(s)
bool orientNormalsWithMST(unsigned kNN=6, ecvProgressDialog *pDlg=nullptr)
Orient the normals with a Minimum Spanning Tree.
int getCurrentDisplayedScalarFieldIndex() const
Returns the currently displayed scalar field index (or -1 if none)
void unallocateNorms()
Erases the cloud normals.
void deleteScalarField(int index) override
Deletes a specific scalar field.
bool convertCurrentScalarFieldToColors(bool mixWithExistingColor=false)
ccPointCloud * partialClone(const cloudViewer::ReferenceCloud *selection, int *warnings=nullptr, bool withChildEntities=true) const
Creates a new point cloud object from a ReferenceCloud (selection)
bool hasScalarFields() const override
Returns whether one or more scalar fields are instantiated.
void invertNormals()
Inverts normals (if any)
static Operation GetOperationByName(QString name)
Returns the operation enumerator based on its name.
static bool Apply(ccPointCloud *cloud, Operation op, int sf1Idx, bool inplace, SF2 *sf2=0, QWidget *parent=0)
Applies operation on a given cloud.
Operation
Arithmetic operations.
A scalar field associated to display-related parameters.
void setMinDisplayed(ScalarType val)
Sets the minimum displayed value.
void setColorScale(ccColorScale::Shared scale)
Sets associated color scale.
void setSymmetricalScale(bool state)
Sets whether the color scale should be symmetrical or not.
void computeMinAndMax() override
Determines the min and max values.
void setSaturationStart(ScalarType val)
Sets the value at which to start color gradient.
Shifted entity interface.
virtual void setGlobalScale(double scale)
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
virtual const CCVector3d & getGlobalShift() const
Returns the shift applied to original coordinates.
virtual double getGlobalScale() const
Returns the scale applied to original coordinates.
Vector3Tpl< T > getCenter() const
Returns center.
static const int MAX_OCTREE_LEVEL
Max octree subdivision level.
virtual unsigned size() const =0
Returns the number of points.
A generic class to handle a probability distribution.
virtual const char * getName() const =0
Returns distribution name.
const CCVector3 * getGravityCenter()
Returns gravity center.
GeomFeature
Geometric feature computed from eigen values/vectors.
CurvatureType
Curvature type.
The Normal/Gaussian statistical distribution.
bool setParameters(ScalarType _mu, ScalarType _sigma2)
Sets the distribution parameters.
ScalarField * getCurrentOutScalarField() const
Returns the scalar field currently associated to the cloud output.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
int getCurrentInScalarFieldIndex()
Returns current INPUT scalar field index (or -1 if none)
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
unsigned getNumberOfScalarFields() const
Returns the number of associated (and active) scalar fields.
void setCurrentScalarField(int index)
Sets both the INPUT & OUTPUT scalar field.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
int getCurrentOutScalarFieldIndex()
Returns current OUTPUT scalar field index (or -1 if none)
unsigned size() const override
void setCurrentInScalarField(int index)
Sets the INPUT scalar field.
ScalarField * getCurrentInScalarField() const
Returns the scalar field currently associated to the cloud input.
void setClosed(bool state)
Sets whether the polyline is closed or not.
A very simple point cloud (no point duplication)
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
unsigned size() const override
Returns the number of points.
virtual void clear(bool releaseMemory=false)
Clears the cloud.
A simple scalar field (to be associated to a point cloud)
virtual void computeMinAndMax()
Determines the min and max values.
ScalarType getMin() const
Returns the minimum value.
std::size_t countValidValues() const
Returns the number of valid values in this scalar field.
void computeMeanAndVariance(ScalarType &mean, ScalarType *variance=nullptr) const
const char * getName() const
Returns scalar field name.
void setName(const char *name)
Sets scalar field name.
static bool ValidValue(ScalarType value)
Returns whether a scalar value is valid or not.
ScalarType getMax() const
Returns the maximum value.
The Weibull statistical parametric distribution.
bool setParameters(ScalarType a, ScalarType b, ScalarType valueShift=0)
Sets the distribution parameters.
static void Init(QListWidget *textDisplay=nullptr, QWidget *parentWidget=nullptr, MainWindow *parentWindow=nullptr, bool redirectToStdOut=false)
Inits console (and optionaly associates it with a text output widget)
Graphical progress indicator (thread-safe)
constexpr char COMMAND_FLIP_TRIANGLES[]
constexpr char COMMAND_CLOSEST_POINT_SET[]
constexpr char COMMAND_REMOVE_SCAN_GRIDS[]
constexpr char COMMAND_DENSITY_TYPE[]
constexpr char COMMAND_APPLY_TRANSFORMATION[]
constexpr char COMMAND_SAVE_MESHES[]
constexpr char OPTION_ALL[]
constexpr char COMMAND_REMOVE_NORMALS[]
constexpr char COMMAND_SAMPLE_MESH[]
constexpr char COMMAND_COMPUTE_GRIDDED_NORMALS[]
constexpr char OPTION_MESH[]
constexpr char COMMAND_RENAME_SF[]
constexpr char COMMAND_PLY_EXPORT_FORMAT[]
constexpr char COMMAND_ASCII_EXPORT_PRECISION[]
constexpr char OPTION_FORCE[]
constexpr char COMMAND_CLOUD_EXPORT_FORMAT[]
constexpr char OPTION_PERCENT[]
constexpr char COMMAND_MERGE_MESHES[]
constexpr char COMMAND_REMOVE_ALL_SFS[]
constexpr char COMMAND_CROP_OUTSIDE[]
constexpr char COMMAND_POP_MESHES[]
constexpr char COMMAND_DELAUNAY_MAX_EDGE_LENGTH[]
constexpr char COMMAND_CONVERT_NORMALS_TO_DIP[]
constexpr char COMMAND_VERBOSITY[]
constexpr char OPTION_FIRST[]
constexpr char COMMAND_ICP_USE_DATA_SF_AS_WEIGHT[]
constexpr char COMMAND_REMOVE_RGB[]
constexpr char OPTION_REGEX[]
constexpr char COMMAND_DELAUNAY_AA[]
constexpr char OPTION_FILE_NAMES[]
constexpr char COMMAND_EXTRACT_CC[]
spatial step / octree level)
constexpr char COMMAND_MESH_EXPORT_FORMAT[]
constexpr char OPTION_MODEL[]
constexpr char COMMAND_EXTRACT_VERTICES[]
constexpr char OPTION_ALL_AT_ONCE[]
constexpr char COMMAND_ASCII_EXPORT_ADD_PTS_COUNT[]
constexpr char COMMAND_CLEAR_MESHES[]
int GetScalarFieldIndex(ccPointCloud *cloud, int sfIndex, const QString &sfName, bool minusOneMeansCurrent=false)
constexpr char COMMAND_APPROX_DENSITY[]
constexpr char OPTION_SIGMA_SF[]
constexpr char COMMAND_ICP_MIN_ERROR_DIIF[]
constexpr char COMMAND_ICP_RANDOM_SAMPLING_LIMIT[]
constexpr char COMMAND_CROP_2D[]
constexpr char COMMAND_OPEN[]
cloudViewer::ScalarField * GetScalarField(ccPointCloud *cloud, int sfIndex, const QString &sfName, bool minusOneMeansCurrent=false)
constexpr char COMMAND_EXPORT_EXTENSION[]
constexpr char COMMAND_AUTO_SAVE[]
static bool GetSFIndexOrName(ccCommandLineInterface &cmd, int &sfIndex, QString &sfName, bool allowMinusOne=false)
constexpr char OPTION_ON[]
constexpr char COMMAND_ICP[]
constexpr char COMMAND_ICP_USE_MODEL_SF_AS_WEIGHT[]
constexpr char COMMAND_DELAUNAY_BF[]
constexpr char COMMAND_SAVE_CLOUDS[]
constexpr char COMMAND_SUBSAMPLE[]
constexpr char COMMAND_ORIENT_NORMALS[]
constexpr char COMMAND_POP_CLOUDS[]
constexpr char COMMAND_MOMENT[]
constexpr char COMMAND_MAX_THREAD_COUNT[]
constexpr char COMMAND_BEST_FIT_PLANE_KEEP_LOADED[]
constexpr char COMMAND_DEBUG[]
constexpr char COMMAND_MESH_VOLUME[]
constexpr char OPTION_BILATERAL[]
constexpr char OPTION_BLEND_GRAYSCALE[]
constexpr char COMMAND_LOG_FILE[]
constexpr char COMMAND_COMPUTE_OCTREE_NORMALS[]
constexpr char OPTION_MEAN[]
constexpr char COMMAND_CURVATURE[]
constexpr char COMMAND_C2M_DIST[]
constexpr char COMMAND_BEST_FIT_PLANE_MAKE_HORIZ[]
constexpr char OPTION_SF[]
constexpr char OPTION_BURNT_COLOR_THRESHOLD[]
constexpr char COMMAND_BEST_FIT_PLANE[]
constexpr char COMMAND_SF_ARITHMETIC[]
constexpr char COMMAND_FILTER[]
constexpr char COMMAND_ICP_ROT[]
constexpr char COMMAND_FILTER_SF_BY_VALUE[]
constexpr char COMMAND_ICP_ENABLE_FARTHEST_REMOVAL[]
constexpr char COMMAND_SOR_FILTER[]
constexpr char COMMAND_INVERT_NORMALS[]
constexpr char COMMAND_ICP_REFERENCE_IS_FIRST[]
constexpr char COMMAND_FEATURE[]
constexpr char COMMAND_REMOVE_SF[]
constexpr char OPTION_NOT[]
constexpr char COMMAND_ROUGHNESS_UP_DIR[]
constexpr char COMMAND_SF_GRADIENT[]
constexpr char COMMAND_DELAUNAY[]
constexpr char COMMAND_HIERARCHY_EXPORT_FORMAT[]
constexpr char COMMAND_ICP_ITERATION_COUNT[]
constexpr char COMMAND_CONVERT_NORMALS_TO_SFS[]
constexpr char COMMAND_RGB_CONVERT_TO_SF[]
constexpr char COMMAND_SF_COLOR_SCALE[]
constexpr char COMMAND_ASCII_EXPORT_ADD_COL_HEADER[]
constexpr char COMMAND_C2X_MAX_DISTANCE[]
constexpr char COMMAND_STAT_TEST[]
constexpr char COMMAND_ASCII_EXPORT_SEPARATOR[]
constexpr char OPTION_ORIENT[]
constexpr char COMMAND_ROUGHNESS[]
constexpr char COMMAND_C2C_DIST[]
constexpr char OPTION_SIGMA[]
constexpr char COMMAND_C2C_LOCAL_MODEL[]
constexpr char COMMAND_NO_TIMESTAMP[]
constexpr char COMMAND_CLEAR[]
constexpr char COMMAND_CLEAR_CLOUDS[]
constexpr char COMMAND_SET_ACTIVE_SF[]
constexpr char COMMAND_C2M_DIST_FLIP_NORMALS[]
constexpr char OPTION_LAST[]
constexpr char COMMAND_COORD_TO_SF[]
constexpr char COMMAND_SF_CONVERT_TO_RGB[]
constexpr char COMMAND_ICP_ADJUST_SCALE[]
constexpr char COMMAND_MATCH_BB_CENTERS[]
constexpr char OPTION_GAUSSIAN[]
constexpr char OPTION_OFF[]
constexpr char COMMAND_OPEN_SKIP_LINES[]
static bool ReadDensityType(ccCommandLineInterface &cmd, cloudViewer::GeometricalAnalysisTools::Density &density)
constexpr char COMMAND_CLEAR_NORMALS[]
constexpr char COMMAND_C2X_OCTREE_LEVEL[]
constexpr char COMMAND_SF_OP[]
constexpr char OPTION_NUMBER_OF_POINTS[]
constexpr char OPTION_RGB[]
constexpr char COMMAND_CROP[]
constexpr char COMMAND_COLOR_BANDING[]
constexpr char COMMAND_DROP_GLOBAL_SHIFT[]
constexpr char COMMAND_ICP_OVERLAP[]
constexpr char OPTION_MEDIAN[]
constexpr char OPTION_CLOUD[]
constexpr char COMMAND_MERGE_CLOUDS[]
constexpr char COMMAND_VOLUME_TO_FILE[]
constexpr char COMMAND_DENSITY[]
constexpr char COMMAND_C2C_SPLIT_XYZ[]
constexpr char OPTION_USE_ACTIVE_SF[]
#define CC_CHI2_DISTANCES_DEFAULT_SF_NAME
#define CC_CONNECTED_COMPONENTS_DEFAULT_LABEL_NAME
QTextStream & endl(QTextStream &stream)
bool exportNormalToSF(const ccHObject::Container &selectedEntities, QWidget *parent, bool *exportDimensions)
bool convertNormalsTo(const ccHObject::Container &selectedEntities, NORMAL_CONVERSION_DEST dest)
Converts a cloud's normals.
bool ApplyCCLibAlgorithm(CC_LIB_ALGORITHM algo, ccHObject::Container &entities, QWidget *parent, void **additionalParameters)
bool ComputeGeomCharacteristic(cloudViewer::GeometricalAnalysisTools::GeomCharacteristic c, int subOption, PointCoordinateType radius, ccHObject::Container &entities, const CCVector3 *roughnessUpDir, QWidget *parent, ecvProgressDialog *progressDialog)
static const std::string path
bool GreaterThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
@ DELAUNAY_2D_BEST_LS_PLANE
@ DELAUNAY_2D_AXIS_ALIGNED
std::vector< ReferenceCloud * > ReferenceCloudContainer
A standard container to store several subsets of points.
std::string toString(T x)
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Loaded cloud description.
Loaded entity description.
virtual ccHObject * getEntity()=0
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
CommandComputeMeshVolume()
bool process(ccCommandLineInterface &cmd) override
Main process.
CommandConvertNormalsToDipAndDipDir()
CommandConvertNormalsToSFs()
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
CommandDist(bool cloud2meshDist, const QString &name, const QString &keyword)
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
CommandForceNormalsComputation()
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
CommandMatchBestFitPlane()
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
CommandOrientNormalsMST()
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool removeSF(int sfIndex, ccPointCloud &pc)
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
CommandSave(const QString &name, const QString &keyword)
static void SetFileDesc(CLEntityDesc &desc, const QString &fileName)
static bool ParseFileNames(ccCommandLineInterface &cmd, QStringList &fileNames)
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.
bool process(ccCommandLineInterface &cmd) override
Main process.