ccCommandLineParser.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021
  1. #include "ccCommandLineParser.h"
  2. //Local
  3. #include "ccCommandCrossSection.h"
  4. #include "ccCommandLineCommands.h"
  5. #include "ccCommandRaster.h"
  6. #include "ccPluginInterface.h"
  7. //qCC_db
  8. #include <ccGenericMesh.h>
  9. #include <ccHObjectCaster.h>
  10. #include <ccProgressDialog.h>
  11. //qCC_io
  12. #include <AsciiFilter.h>
  13. #include <BinFilter.h>
  14. //qCC
  15. #include "ccConsole.h"
  16. #include <ui_commandLineDlg.h>
  17. //Qt
  18. #include <QDateTime>
  19. #include <QElapsedTimer>
  20. #include <QMessageBox>
  21. //system
  22. #include <unordered_set>
  23. //commands
  24. constexpr char COMMAND_HELP[] = "HELP";
  25. constexpr char COMMAND_SILENT_MODE[] = "SILENT";
  26. /*****************************************************/
  27. /*************** ccCommandLineParser *****************/
  28. /*****************************************************/
  29. void ccCommandLineParser::printVerbose(const QString& message) const
  30. {
  31. ccConsole::PrintVerbose(message);
  32. }
  33. void ccCommandLineParser::print(const QString& message) const
  34. {
  35. ccConsole::Print(message);
  36. }
  37. void ccCommandLineParser::printHigh(const QString& message) const
  38. {
  39. ccConsole::PrintHigh(message);
  40. }
  41. void ccCommandLineParser::printDebug(const QString& message) const
  42. {
  43. ccConsole::PrintDebug(message);
  44. }
  45. void ccCommandLineParser::warning(const QString& message) const
  46. {
  47. ccConsole::Warning(message);
  48. }
  49. void ccCommandLineParser::warningDebug(const QString& message) const
  50. {
  51. ccConsole::WarningDebug(message);
  52. }
  53. bool ccCommandLineParser::error(const QString& message) const
  54. {
  55. ccConsole::Error(message);
  56. return false;
  57. }
  58. bool ccCommandLineParser::errorDebug(const QString& message) const
  59. {
  60. ccConsole::ErrorDebug(message);
  61. return false;
  62. }
  63. int ccCommandLineParser::Parse(const QStringList& arguments, ccPluginInterfaceList& plugins)
  64. {
  65. if (arguments.size() < 2)
  66. {
  67. assert(false);
  68. return EXIT_SUCCESS;
  69. }
  70. //load arguments
  71. QScopedPointer<ccCommandLineParser> parser(new ccCommandLineParser);
  72. parser->registerBuiltInCommands();
  73. ccConsole::SetRefreshCycle(200);
  74. // 'massage' the arguments to properly handle single quotes
  75. {
  76. bool insideSingleQuoteSection = false;
  77. QString buffer;
  78. static const QChar SingleQuote{ '\'' };
  79. for (int currentArgIndex = 1; currentArgIndex < arguments.size(); ++currentArgIndex) // start from 1, as the first argument is always the executable file
  80. {
  81. QString arg = arguments[currentArgIndex];
  82. // argument starts with a single quote
  83. if (!insideSingleQuoteSection && arg.startsWith(SingleQuote))
  84. {
  85. if (arg.endsWith(SingleQuote))
  86. {
  87. // nothing to do, non-truncated argument
  88. }
  89. else
  90. {
  91. // we'll collect the next pieces to get the full argument
  92. insideSingleQuoteSection = true;
  93. buffer = arg.mid(1); // remove the single quote
  94. }
  95. }
  96. else if (insideSingleQuoteSection)
  97. {
  98. buffer += QChar(' ') + arg; // append the current argument to the previous one(s)
  99. if (arg.endsWith(SingleQuote))
  100. {
  101. insideSingleQuoteSection = false;
  102. arg = buffer.left(buffer.length() - 1); // remove the single quote
  103. }
  104. }
  105. if (!insideSingleQuoteSection)
  106. {
  107. parser->arguments().append(arg);
  108. }
  109. }
  110. if (insideSingleQuoteSection)
  111. {
  112. // the single quote section was not closed...
  113. parser->warning("Probably malformed command (missing closing simple quote)");
  114. // ...still, we'll try to proceed
  115. parser->arguments().append(buffer);
  116. }
  117. }
  118. //specific command: silent mode (will prevent the console dialog from appearing!
  119. if (ccCommandLineInterface::IsCommand(parser->arguments().front(), COMMAND_SILENT_MODE))
  120. {
  121. parser->arguments().pop_front();
  122. parser->toggleSilentMode(true);
  123. }
  124. QScopedPointer<QDialog> consoleDlg(nullptr);
  125. if (!parser->silentMode())
  126. {
  127. //show console
  128. consoleDlg.reset(new QDialog);
  129. Ui_commandLineDlg commandLineDlg;
  130. commandLineDlg.setupUi(consoleDlg.data());
  131. consoleDlg->show();
  132. ccConsole::Init(commandLineDlg.consoleWidget, consoleDlg.data());
  133. parser->fileLoadingParams().parentWidget = consoleDlg.data();
  134. QApplication::processEvents(); //Get rid of the spinner
  135. }
  136. else
  137. {
  138. //allows ccLog/ccConsole or ccCommandLineParser (print, warning, error) to output to the console
  139. ccConsole::Init(nullptr, nullptr, nullptr, true);
  140. }
  141. //load the plugins commands
  142. for ( ccPluginInterface *plugin : plugins )
  143. {
  144. if (!plugin)
  145. {
  146. assert(false);
  147. continue;
  148. }
  149. plugin->registerCommands(parser.data());
  150. }
  151. //parse input
  152. int result = parser->start(consoleDlg.data());
  153. if (!parser->silentMode())
  154. {
  155. if (result == EXIT_SUCCESS)
  156. QMessageBox::information(consoleDlg.data(), "Processed finished", "Job done");
  157. else
  158. QMessageBox::warning(consoleDlg.data(), "Processed finished", "An error occurred! Check console");
  159. }
  160. //release the parser before the console (as its dialogs may be chidren of the console)
  161. parser->cleanup();
  162. parser.reset();
  163. ccConsole::ReleaseInstance();
  164. return result;
  165. }
  166. ccCommandLineParser::ccCommandLineParser()
  167. : ccCommandLineInterface()
  168. , m_cloudExportFormat(BinFilter::GetFileFilter())
  169. , m_cloudExportExt(BinFilter::GetDefaultExtension())
  170. , m_meshExportFormat(BinFilter::GetFileFilter())
  171. , m_meshExportExt(BinFilter::GetDefaultExtension())
  172. , m_hierarchyExportFormat(BinFilter::GetFileFilter())
  173. , m_hierarchyExportExt(BinFilter::GetDefaultExtension())
  174. , m_orphans("orphans")
  175. , m_progressDialog(nullptr)
  176. , m_parentWidget(nullptr)
  177. {
  178. }
  179. ccCommandLineParser::~ccCommandLineParser()
  180. {
  181. if (m_progressDialog)
  182. {
  183. m_progressDialog->close();
  184. m_progressDialog->deleteLater();
  185. }
  186. }
  187. bool ccCommandLineParser::registerCommand(Command::Shared command)
  188. {
  189. if (!command)
  190. {
  191. assert(false);
  192. return false;
  193. }
  194. if (m_commands.contains(command->m_keyword))
  195. {
  196. assert(false);
  197. warning(QString("Internal error: keyword '%1' already registered (by command '%2')").arg(command->m_keyword, m_commands[command->m_keyword]->m_name));
  198. return false;
  199. }
  200. m_commands.insert(command->m_keyword, command);
  201. return true;
  202. }
  203. QString ccCommandLineParser::getExportFilename( const CLEntityDesc& entityDesc,
  204. QString extension/*=QString()*/,
  205. QString suffix/*=QString()*/,
  206. QString* baseOutputFilename/*=nullptr*/,
  207. bool forceNoTimestamp/*=false*/) const
  208. {
  209. //fetch the real entity
  210. const ccHObject* entity = entityDesc.getEntity();
  211. if (!entity)
  212. {
  213. assert(false);
  214. warning("[getExportFilename] Internal error: invalid input entity!");
  215. return QString();
  216. }
  217. //sub-item?
  218. if (entityDesc.indexInFile >= 0)
  219. {
  220. if (suffix.isEmpty())
  221. suffix = QString("%1").arg(entityDesc.indexInFile);
  222. else
  223. suffix.prepend(QString("%1_").arg(entityDesc.indexInFile));
  224. }
  225. QString baseName = entityDesc.basename;
  226. if (!suffix.isEmpty())
  227. {
  228. baseName += QString("_") + suffix;
  229. }
  230. QString outputFilename = baseName;
  231. if (m_addTimestamp && !forceNoTimestamp)
  232. {
  233. outputFilename += QString("_%1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh'h'mm_ss_zzz"));
  234. }
  235. if (!extension.isEmpty())
  236. {
  237. outputFilename += '.' + extension;
  238. }
  239. if (baseOutputFilename)
  240. {
  241. *baseOutputFilename = outputFilename;
  242. }
  243. if (!entityDesc.path.isEmpty())
  244. {
  245. outputFilename.prepend(entityDesc.path + '/');
  246. }
  247. return outputFilename;
  248. }
  249. QString ccCommandLineParser::exportEntity( CLEntityDesc& entityDesc,
  250. const QString& suffix/*=QString()*/,
  251. QString* baseOutputFilename/*=nullptr*/,
  252. ccCommandLineInterface::ExportOptions options/*ExportOptiopn::NoOption*/)
  253. {
  254. print("[SAVING]");
  255. //fetch the real entity
  256. ccHObject* entity = entityDesc.getEntity();
  257. if (!entity)
  258. {
  259. assert(false);
  260. return "[ExportEntity] Internal error: invalid input entity!";
  261. }
  262. bool anyForced = options.testFlag(ExportOption::ForceCloud) | options.testFlag(ExportOption::ForceHierarchy) | options.testFlag(ExportOption::ForceMesh);
  263. //specific case: clouds
  264. bool isCloud = entity->isA(CC_TYPES::POINT_CLOUD) || entityDesc.getCLEntityType() == CL_ENTITY_TYPE::CLOUD;
  265. //specific case: mesh
  266. bool isMesh = entity->isKindOf(CC_TYPES::MESH) || entityDesc.getCLEntityType() == CL_ENTITY_TYPE::MESH;
  267. QString extension = isCloud ? m_cloudExportExt : isMesh ? m_meshExportExt : m_hierarchyExportExt;
  268. QString format = isCloud ? m_cloudExportFormat : isMesh ? m_meshExportFormat : m_hierarchyExportFormat;
  269. if (anyForced)
  270. {
  271. if (options.testFlag(ExportOption::ForceCloud))
  272. {
  273. extension = m_cloudExportExt;
  274. format = m_cloudExportFormat;
  275. }
  276. if (options.testFlag(ExportOption::ForceMesh))
  277. {
  278. extension = m_meshExportExt;
  279. format = m_meshExportFormat;
  280. }
  281. if (options.testFlag(ExportOption::ForceHierarchy))
  282. {
  283. extension = m_hierarchyExportExt;
  284. format = m_hierarchyExportFormat;
  285. }
  286. }
  287. QString outputFilename = getExportFilename( entityDesc,
  288. extension,
  289. suffix,
  290. baseOutputFilename,
  291. options.testFlag(ExportOption::ForceNoTimestamp) );
  292. if (outputFilename.isEmpty())
  293. {
  294. return QString();
  295. }
  296. //update the entity name as well
  297. {
  298. QString entName = entity->getName();
  299. if (entName.isEmpty())
  300. {
  301. entName = entityDesc.basename;
  302. }
  303. if (!suffix.isEmpty())
  304. {
  305. entName += QString("_") + suffix;
  306. }
  307. entity->setName(entName);
  308. }
  309. bool tempDependencyCreated = false;
  310. ccGenericMesh* mesh = nullptr;
  311. if (entity->isKindOf(CC_TYPES::MESH) && m_meshExportFormat == BinFilter::GetFileFilter())
  312. {
  313. //in a BIN file we must save the vertices cloud as well if it's not a child of the mesh!
  314. mesh = static_cast<ccGenericMesh*>(entity);
  315. ccGenericPointCloud* vertices = mesh->getAssociatedCloud();
  316. if (vertices && !mesh->isAncestorOf(vertices))
  317. {
  318. //we save the cloud first!
  319. vertices->addChild(mesh, ccHObject::DP_NONE); //we simply add a fake dependency
  320. entity = vertices;
  321. tempDependencyCreated = true;
  322. }
  323. }
  324. //save file
  325. FileIOFilter::SaveParameters parameters;
  326. {
  327. //no dialog by default for command line mode!
  328. parameters.alwaysDisplaySaveDialog = false;
  329. if (!silentMode() && ccConsole::TheInstance())
  330. {
  331. parameters.parentWidget = ccConsole::TheInstance()->parentWidget();
  332. }
  333. }
  334. #ifdef _DEBUG
  335. print("Output filename: " + outputFilename);
  336. #endif
  337. CC_FILE_ERROR result = FileIOFilter::SaveToFile(entity,
  338. outputFilename,
  339. parameters,
  340. format);
  341. //restore input state!
  342. if (tempDependencyCreated)
  343. {
  344. if (mesh && entity)
  345. {
  346. entity->detachChild(mesh);
  347. }
  348. else
  349. {
  350. assert(false);
  351. }
  352. }
  353. return (result != CC_FERR_NO_ERROR ? QString("Failed to save result in file '%1'").arg(outputFilename) : QString());
  354. }
  355. template<class EntityDesc > bool SelectEntities(ccCommandLineInterface::SelectEntitiesOptions options,
  356. const ccCommandLineParser& cmd,
  357. std::vector<EntityDesc>& selectedEntities,
  358. std::vector<EntityDesc>& unselectedEntities,
  359. QString entityType)
  360. {
  361. //early abort if no cloud found
  362. if (selectedEntities.empty() && unselectedEntities.empty())
  363. {
  364. //do not stop execution, just warn the user and return
  365. cmd.warning(QObject::tr("\tNo %1 loaded. Load some with the -O command").arg(entityType));
  366. return true;
  367. }
  368. if (options.selectRegex && !options.regex.isValid())
  369. {
  370. return cmd.error(QObject::tr("Regex string invalid: %1").arg(options.regex.errorString()));
  371. }
  372. try
  373. {
  374. //store everthyng in the unselected vector
  375. unselectedEntities.insert(unselectedEntities.end(), selectedEntities.begin(), selectedEntities.end());
  376. selectedEntities.clear();
  377. //sort the unselected clouds by uniqueID (so as to restore the order in which they were loaded/created)
  378. std::sort(unselectedEntities.begin(), unselectedEntities.end(), [](const EntityDesc& a, const EntityDesc& b) { return (a.getEntity()->getUniqueID() < b.getEntity()->getUniqueID()); });
  379. //put elements to the front facing vector
  380. unsigned index = 0;
  381. assert(!unselectedEntities.empty()); // we have tested above that neither selectedEntities and unselectedEntities are both empty
  382. size_t lastIndex = unselectedEntities.size() - 1;
  383. for (typename std::vector<EntityDesc>::iterator it = unselectedEntities.begin(); it != unselectedEntities.end();)
  384. {
  385. QString nameToValidate = QObject::tr("%1/%2").arg(it->basename).arg(it->getEntity()->getName());
  386. bool toBeSelected = false;
  387. if (!options.reverse)
  388. {
  389. //first {n}
  390. if (options.selectFirst && index < options.firstNr)
  391. {
  392. toBeSelected = true;
  393. }
  394. //last {n}
  395. if (options.selectLast && index > lastIndex - options.lastNr)
  396. {
  397. toBeSelected = true;
  398. }
  399. }
  400. else
  401. {
  402. //not first {n}
  403. if (options.selectFirst && index >= options.firstNr && !options.selectLast)
  404. {
  405. toBeSelected = true;
  406. }
  407. //not last {n}
  408. if (options.selectLast && index <= lastIndex - options.lastNr && !options.selectFirst)
  409. {
  410. toBeSelected = true;
  411. }
  412. //not first and not last
  413. if (options.selectFirst && options.selectLast && index >= options.firstNr && index <= lastIndex - options.lastNr)
  414. {
  415. toBeSelected = true;
  416. }
  417. }
  418. //regex has higher priority than first/last overwrite
  419. if (options.selectRegex)
  420. {
  421. if (options.regex.indexIn(nameToValidate) > -1)
  422. {
  423. //regex matched
  424. toBeSelected = !options.reverse;
  425. }
  426. else
  427. {
  428. //regex not matched
  429. toBeSelected = options.reverse;
  430. }
  431. }
  432. //selectAll has higher priority than first/last/regex overwrite
  433. if (options.selectAll)
  434. {
  435. toBeSelected = !options.reverse;
  436. }
  437. if (toBeSelected)
  438. {
  439. cmd.print(QObject::tr("\t[*] UID: %2 name: %1").arg(nameToValidate).arg(it->getEntity()->getUniqueID()));
  440. selectedEntities.push_back(*it);
  441. it = unselectedEntities.erase(it);
  442. }
  443. else
  444. {
  445. cmd.print(QObject::tr("\t[ ] UID: %2 name: %1").arg(nameToValidate).arg(it->getEntity()->getUniqueID()));
  446. ++it;
  447. }
  448. index++;
  449. }
  450. }
  451. catch (const std::bad_alloc&)
  452. {
  453. return cmd.error(QObject::tr("Not enough memory"));
  454. }
  455. return true;
  456. }
  457. bool ccCommandLineParser::selectClouds(const SelectEntitiesOptions& options)
  458. {
  459. return SelectEntities(options, *this, m_clouds, m_unselectedClouds, "cloud");
  460. }
  461. bool ccCommandLineParser::selectMeshes(const SelectEntitiesOptions& options)
  462. {
  463. return SelectEntities(options, *this, m_meshes, m_unselectedMeshes, "mesh");
  464. }
  465. void ccCommandLineParser::removeClouds(bool onlyLast/*=false*/)
  466. {
  467. while (!m_clouds.empty())
  468. {
  469. delete m_clouds.back().pc;
  470. m_clouds.pop_back();
  471. if (onlyLast)
  472. break;
  473. }
  474. }
  475. void ccCommandLineParser::removeMeshes(bool onlyLast/*=false*/)
  476. {
  477. while (!m_meshes.empty())
  478. {
  479. delete m_meshes.back().mesh;
  480. m_meshes.pop_back();
  481. if (onlyLast)
  482. break;
  483. }
  484. }
  485. //! Whether Global (coordinate) shift has already been defined
  486. static bool s_firstCoordinatesShiftEnabled = false;
  487. //! Global shift (if defined)
  488. static CCVector3d s_firstGlobalShift;
  489. //! First time the global shift is set/defined
  490. static bool s_globalShiftFirstTime = true;
  491. void ccCommandLineParser::setGlobalShiftOptions(const GlobalShiftOptions& globalShiftOptions)
  492. {
  493. //default Global Shift handling parameters
  494. m_loadingParameters.shiftHandlingMode = ccGlobalShiftManager::NO_DIALOG;
  495. m_loadingParameters.coordinatesShiftEnabled = false;
  496. m_loadingParameters.coordinatesShift = CCVector3d(0, 0, 0);
  497. switch (globalShiftOptions.mode)
  498. {
  499. case GlobalShiftOptions::AUTO_GLOBAL_SHIFT:
  500. //let CC handle the global shift automatically
  501. m_loadingParameters.shiftHandlingMode = ccGlobalShiftManager::NO_DIALOG_AUTO_SHIFT;
  502. break;
  503. case GlobalShiftOptions::FIRST_GLOBAL_SHIFT:
  504. //use the first encountered global shift value (if any)
  505. if (s_globalShiftFirstTime)
  506. {
  507. ccLog::Warning("Can't reuse the first Global Shift (no global shift set yet)");
  508. m_loadingParameters.shiftHandlingMode = ccGlobalShiftManager::NO_DIALOG_AUTO_SHIFT;
  509. }
  510. else
  511. {
  512. m_loadingParameters.coordinatesShiftEnabled = s_firstCoordinatesShiftEnabled;
  513. m_loadingParameters.coordinatesShift = s_firstGlobalShift;
  514. }
  515. break;
  516. case GlobalShiftOptions::CUSTOM_GLOBAL_SHIFT:
  517. //set the user defined shift vector as default shift information
  518. m_loadingParameters.coordinatesShiftEnabled = true;
  519. m_loadingParameters.coordinatesShift = globalShiftOptions.customGlobalShift;
  520. break;
  521. default:
  522. //nothing to do
  523. break;
  524. }
  525. }
  526. void ccCommandLineParser::updateInteralGlobalShift(const GlobalShiftOptions& globalShiftOptions)
  527. {
  528. if (globalShiftOptions.mode != GlobalShiftOptions::NO_GLOBAL_SHIFT)
  529. {
  530. if (s_globalShiftFirstTime)
  531. {
  532. // remember the first Global Shift parameters used
  533. s_firstCoordinatesShiftEnabled = m_loadingParameters.coordinatesShiftEnabled;
  534. s_firstGlobalShift = m_loadingParameters.coordinatesShift;
  535. s_globalShiftFirstTime = false;
  536. }
  537. }
  538. }
  539. bool ccCommandLineParser::importFile(QString filename, const GlobalShiftOptions& globalShiftOptions, FileIOFilter::Shared filter)
  540. {
  541. printHigh(QString("Opening file: '%1'").arg(filename));
  542. setGlobalShiftOptions(globalShiftOptions);
  543. CC_FILE_ERROR result = CC_FERR_NO_ERROR;
  544. ccHObject* db = nullptr;
  545. if (filter)
  546. {
  547. db = FileIOFilter::LoadFromFile(filename, m_loadingParameters, filter, result);
  548. }
  549. else
  550. {
  551. db = FileIOFilter::LoadFromFile(filename, m_loadingParameters, result, QString());
  552. }
  553. if (!db)
  554. {
  555. return false/*cmd.error(QString("Failed to open file '%1'").arg(filename))*/; //Error message already issued
  556. }
  557. updateInteralGlobalShift(globalShiftOptions);
  558. std::unordered_set<unsigned> verticesIDs;
  559. //first look for meshes inside loaded DB (so that we don't consider mesh vertices as clouds!)
  560. {
  561. ccHObject::Container meshes;
  562. size_t count = 0;
  563. //first look for all REAL meshes (so as to no consider sub-meshes)
  564. if (db->filterChildren(meshes, true, CC_TYPES::MESH, true) != 0)
  565. {
  566. count += meshes.size();
  567. for (size_t i = 0; i < meshes.size(); ++i)
  568. {
  569. ccGenericMesh* mesh = ccHObjectCaster::ToGenericMesh(meshes[i]);
  570. if (mesh->getParent())
  571. {
  572. mesh->getParent()->detachChild(mesh);
  573. }
  574. ccGenericPointCloud* vertices = mesh->getAssociatedCloud();
  575. if (vertices)
  576. {
  577. verticesIDs.insert(vertices->getUniqueID());
  578. print(QString("Found one mesh with %1 faces and %2 vertices: '%3'").arg(mesh->size()).arg(mesh->getAssociatedCloud()->size()).arg(mesh->getName()));
  579. m_meshes.emplace_back(mesh, filename, count == 1 ? -1 : static_cast<int>(i));
  580. }
  581. else
  582. {
  583. delete mesh;
  584. mesh = nullptr;
  585. assert(false);
  586. }
  587. }
  588. }
  589. //then look for the other meshes
  590. meshes.clear();
  591. if (db->filterChildren(meshes, true, CC_TYPES::MESH, false) != 0)
  592. {
  593. size_t countBefore = count;
  594. count += meshes.size();
  595. for (size_t i = 0; i < meshes.size(); ++i)
  596. {
  597. ccGenericMesh* mesh = ccHObjectCaster::ToGenericMesh(meshes[i]);
  598. if (mesh->getParent())
  599. mesh->getParent()->detachChild(mesh);
  600. ccGenericPointCloud* vertices = mesh->getAssociatedCloud();
  601. if (vertices)
  602. {
  603. verticesIDs.insert(vertices->getUniqueID());
  604. print(QString("Found one kind of mesh with %1 faces and %2 vertices: '%3'").arg(mesh->size()).arg(mesh->getAssociatedCloud()->size()).arg(mesh->getName()));
  605. m_meshes.emplace_back(mesh, filename, count == 1 ? -1 : static_cast<int>(countBefore + i));
  606. }
  607. else
  608. {
  609. delete mesh;
  610. mesh = nullptr;
  611. assert(false);
  612. }
  613. }
  614. }
  615. }
  616. //now look for the remaining clouds inside loaded DB
  617. {
  618. ccHObject::Container clouds;
  619. db->filterChildren(clouds, true, CC_TYPES::POINT_CLOUD);
  620. size_t count = clouds.size();
  621. for (size_t i = 0; i < count; ++i)
  622. {
  623. ccPointCloud* pc = static_cast<ccPointCloud*>(clouds[i]);
  624. if (pc->getParent())
  625. {
  626. pc->getParent()->detachChild(pc);
  627. }
  628. //if the cloud is a set of vertices, we ignore it!
  629. if (verticesIDs.find(pc->getUniqueID()) != verticesIDs.end())
  630. {
  631. m_orphans.addChild(pc);
  632. continue;
  633. }
  634. print(QString("Found one cloud with %1 points").arg(pc->size()));
  635. m_clouds.emplace_back(pc, filename, count == 1 ? -1 : static_cast<int>(i));
  636. }
  637. }
  638. delete db;
  639. db = nullptr;
  640. return true;
  641. }
  642. bool ccCommandLineParser::saveClouds(QString suffix/*=QString()*/, bool allAtOnce/*=false*/, const QString* allAtOnceFileName/*=nullptr*/)
  643. {
  644. //all-at-once: all clouds in a single file
  645. if (allAtOnce)
  646. {
  647. FileIOFilter::Shared filter = FileIOFilter::GetFilter(m_cloudExportFormat, false);
  648. bool multiple = false;
  649. if (filter)
  650. {
  651. bool exclusive = true;
  652. filter->canSave(CC_TYPES::POINT_CLOUD, multiple, exclusive);
  653. }
  654. if (multiple)
  655. {
  656. ccHObject tempContainer("Clouds");
  657. {
  658. for (CLCloudDesc& desc : m_clouds)
  659. {
  660. tempContainer.addChild(desc.getEntity(), ccHObject::DP_NONE);
  661. }
  662. }
  663. //save output
  664. CLGroupDesc desc(&tempContainer, "AllClouds", m_clouds.front().path);
  665. if (allAtOnceFileName)
  666. {
  667. CommandSave::SetFileDesc(desc, *allAtOnceFileName);
  668. }
  669. QString errorStr = exportEntity(desc, suffix, nullptr, ExportOption::ForceCloud);
  670. if (!errorStr.isEmpty())
  671. return error(errorStr);
  672. else
  673. return true;
  674. }
  675. else
  676. {
  677. error(QString("The currently selected output format for clouds (%1) doesn't handle multiple entities at once!").arg(m_cloudExportFormat));
  678. //will proceed with the standard way
  679. }
  680. }
  681. //standard way: one file per cloud
  682. {
  683. for (CLCloudDesc& desc : m_clouds)
  684. {
  685. //save output
  686. QString errorStr = exportEntity(desc, suffix);
  687. if (!errorStr.isEmpty())
  688. return error(errorStr);
  689. }
  690. }
  691. return true;
  692. }
  693. bool ccCommandLineParser::saveMeshes(QString suffix/*=QString()*/, bool allAtOnce/*=false*/, const QString* allAtOnceFileName/*=nullptr*/)
  694. {
  695. //all-at-once: all meshes in a single file
  696. if (allAtOnce)
  697. {
  698. FileIOFilter::Shared filter = FileIOFilter::GetFilter(m_meshExportFormat, false);
  699. bool multiple = false;
  700. if (filter)
  701. {
  702. bool exclusive = true;
  703. filter->canSave(CC_TYPES::MESH, multiple, exclusive);
  704. }
  705. if (multiple)
  706. {
  707. ccHObject tempContainer("Meshes");
  708. {
  709. for (auto &mesh : m_meshes)
  710. {
  711. tempContainer.addChild(mesh.getEntity(), ccHObject::DP_NONE);
  712. }
  713. }
  714. //save output
  715. CLGroupDesc desc(&tempContainer, "AllMeshes", m_meshes.front().path);
  716. if (allAtOnceFileName)
  717. {
  718. CommandSave::SetFileDesc(desc, *allAtOnceFileName);
  719. }
  720. QString errorStr = exportEntity(desc, suffix, nullptr, ExportOption::ForceMesh);
  721. if (!errorStr.isEmpty())
  722. return error(errorStr);
  723. else
  724. return true;
  725. }
  726. else
  727. {
  728. error(QString("The currently selected output format for meshes (%1) doesn't handle multiple entities at once!").arg(m_meshExportFormat));
  729. //will proceed with the standard way
  730. }
  731. }
  732. //standard way: one file per mesh
  733. for (auto &mesh : m_meshes)
  734. {
  735. //save output
  736. QString errorStr = exportEntity(mesh, suffix);
  737. if (!errorStr.isEmpty())
  738. return error(errorStr);
  739. }
  740. return true;
  741. }
  742. void ccCommandLineParser::registerBuiltInCommands()
  743. {
  744. registerCommand(Command::Shared(new CommandDebugCmdLine));
  745. registerCommand(Command::Shared(new CommandLoad));
  746. registerCommand(Command::Shared(new CommandLoadCommandFile));
  747. registerCommand(Command::Shared(new CommandSubsample));
  748. registerCommand(Command::Shared(new CommandExtractCCs));
  749. registerCommand(Command::Shared(new CommandCurvature));
  750. registerCommand(Command::Shared(new CommandApproxDensity));
  751. registerCommand(Command::Shared(new CommandDensity));
  752. registerCommand(Command::Shared(new CommandSFGradient));
  753. registerCommand(Command::Shared(new CommandRoughness));
  754. registerCommand(Command::Shared(new CommandApplyTransformation));
  755. registerCommand(Command::Shared(new CommandDropGlobalShift));
  756. registerCommand(Command::Shared(new CommandFilterBySFValue));
  757. registerCommand(Command::Shared(new CommandMergeClouds));
  758. registerCommand(Command::Shared(new CommandMergeMeshes));
  759. registerCommand(Command::Shared(new CommandSetActiveSF));
  760. registerCommand(Command::Shared(new CommandSetGlobalShift));
  761. registerCommand(Command::Shared(new CommandRemoveAllSFs));
  762. registerCommand(Command::Shared(new CommandRemoveSF));
  763. registerCommand(Command::Shared(new CommandRemoveRGB));
  764. registerCommand(Command::Shared(new CommandRemoveNormals));
  765. registerCommand(Command::Shared(new CommandRemoveScanGrids));
  766. registerCommand(Command::Shared(new CommandRemoveSensors));
  767. registerCommand(Command::Shared(new CommandMatchBBCenters));
  768. registerCommand(Command::Shared(new CommandMatchBestFitPlane));
  769. registerCommand(Command::Shared(new CommandOrientNormalsMST));
  770. registerCommand(Command::Shared(new CommandSORFilter));
  771. registerCommand(Command::Shared(new CommandNoiseFilter));
  772. registerCommand(Command::Shared(new CommandRemoveDuplicatePoints));
  773. registerCommand(Command::Shared(new CommandSampleMesh));
  774. registerCommand(Command::Shared(new CommandCompressFWF));
  775. registerCommand(Command::Shared(new CommandExtractVertices));
  776. registerCommand(Command::Shared(new CommandCrossSection));
  777. registerCommand(Command::Shared(new CommandCrop));
  778. registerCommand(Command::Shared(new CommandCrop2D));
  779. registerCommand(Command::Shared(new CommandCoordToSF));
  780. registerCommand(Command::Shared(new CommandSFToCoord));
  781. registerCommand(Command::Shared(new CommandColorBanding));
  782. registerCommand(Command::Shared(new CommandColorLevels));
  783. registerCommand(Command::Shared(new CommandC2MDist));
  784. registerCommand(Command::Shared(new CommandC2CDist));
  785. registerCommand(Command::Shared(new CommandCPS));
  786. registerCommand(Command::Shared(new CommandStatTest));
  787. registerCommand(Command::Shared(new CommandDelaunayTri));
  788. registerCommand(Command::Shared(new CommandSFArithmetic));
  789. registerCommand(Command::Shared(new CommandSFOperation));
  790. registerCommand(Command::Shared(new CommandSFOperationSF));
  791. registerCommand(Command::Shared(new CommandSFInterpolation));
  792. registerCommand(Command::Shared(new CommandColorInterpolation));
  793. registerCommand(Command::Shared(new CommandFilter));
  794. registerCommand(Command::Shared(new CommandRenameEntities));
  795. registerCommand(Command::Shared(new CommandSFRename));
  796. registerCommand(Command::Shared(new CommandSFAddConst));
  797. registerCommand(Command::Shared(new CommandSFAddId));
  798. registerCommand(Command::Shared(new CommandICP));
  799. registerCommand(Command::Shared(new CommandChangeCloudOutputFormat));
  800. registerCommand(Command::Shared(new CommandChangeMeshOutputFormat));
  801. registerCommand(Command::Shared(new CommandChangeHierarchyOutputFormat));
  802. registerCommand(Command::Shared(new CommandChangePLYExportFormat));
  803. registerCommand(Command::Shared(new CommandForceNormalsComputation));
  804. registerCommand(Command::Shared(new CommandSaveClouds));
  805. registerCommand(Command::Shared(new CommandSaveMeshes));
  806. registerCommand(Command::Shared(new CommandAutoSave));
  807. registerCommand(Command::Shared(new CommandLogFile));
  808. registerCommand(Command::Shared(new CommandSelectEntities));
  809. registerCommand(Command::Shared(new CommandClear));
  810. registerCommand(Command::Shared(new CommandClearClouds));
  811. registerCommand(Command::Shared(new CommandPopClouds));
  812. registerCommand(Command::Shared(new CommandClearMeshes));
  813. registerCommand(Command::Shared(new CommandPopMeshes));
  814. registerCommand(Command::Shared(new CommandSetNoTimestamp));
  815. registerCommand(Command::Shared(new CommandVolume25D));
  816. registerCommand(Command::Shared(new CommandRasterize));
  817. registerCommand(Command::Shared(new CommandOctreeNormal));
  818. registerCommand(Command::Shared(new CommandConvertNormalsToDipAndDipDir));
  819. registerCommand(Command::Shared(new CommandConvertNormalsToSFs));
  820. registerCommand(Command::Shared(new CommandConvertNormalsToHSV));
  821. registerCommand(Command::Shared(new CommandClearNormals));
  822. registerCommand(Command::Shared(new CommandInvertNormal));
  823. registerCommand(Command::Shared(new CommandComputeMeshVolume));
  824. registerCommand(Command::Shared(new CommandSFColorScale));
  825. registerCommand(Command::Shared(new CommandSFConvertToRGB));
  826. registerCommand(Command::Shared(new CommandMoment));
  827. registerCommand(Command::Shared(new CommandFeature));
  828. registerCommand(Command::Shared(new CommandRGBConvertToSF));
  829. registerCommand(Command::Shared(new CommandFlipTriangles));
  830. registerCommand(Command::Shared(new CommandSetVerbosity));
  831. }
  832. void ccCommandLineParser::cleanup()
  833. {
  834. removeClouds();
  835. removeMeshes();
  836. }
  837. int ccCommandLineParser::start(QDialog* parent/*=nullptr*/)
  838. {
  839. if (m_arguments.empty())
  840. {
  841. assert(false);
  842. return EXIT_FAILURE;
  843. }
  844. m_parentWidget = parent;
  845. //if (!m_silentMode)
  846. //{
  847. // m_progressDialog = new ccProgressDialog(false, parent);
  848. // //m_progressDialog->setAttribute(Qt::WA_DeleteOnClose);
  849. // m_progressDialog->setAutoClose(false);
  850. // m_progressDialog->hide();
  851. //}
  852. QElapsedTimer eTimer;
  853. eTimer.start();
  854. bool success = true;
  855. while (success && !m_arguments.empty())
  856. {
  857. QApplication::processEvents(); //Without this the console is just a spinner until the end of all processing
  858. QString argument = m_arguments.takeFirst();
  859. if (!argument.startsWith("-"))
  860. {
  861. error(QString("Command expected (commands start with '-'). Found '%1'").arg(argument));
  862. success = false;
  863. break;
  864. }
  865. QString keyword = argument.mid(1).toUpper();
  866. if (m_commands.contains(keyword))
  867. {
  868. assert(m_commands[keyword]);
  869. QElapsedTimer eTimerSubProcess;
  870. eTimerSubProcess.start();
  871. QString processName = m_commands[keyword]->m_name.toUpper();
  872. printHigh(QString("[%1]").arg(processName));
  873. success = m_commands[keyword]->process(*this);
  874. printHigh(QString("[%2] finished in %1 s.").arg(eTimerSubProcess.elapsed() / 1.0e3, 0, 'f', 2).arg(processName));
  875. }
  876. //silent mode (i.e. no console)
  877. else if (keyword == COMMAND_SILENT_MODE)
  878. {
  879. warning(QString("Misplaced command: '%1' (must be first)").arg(COMMAND_SILENT_MODE));
  880. }
  881. else if (keyword == COMMAND_HELP)
  882. {
  883. print("Available commands:");
  884. for (auto it = m_commands.constBegin(); it != m_commands.constEnd(); ++it)
  885. {
  886. print(QString("-%1: %2").arg(it.key().toUpper(), it.value()->m_name));
  887. }
  888. }
  889. else
  890. {
  891. error(QString("Unknown or misplaced command: '%1'").arg(argument));
  892. success = false;
  893. break;
  894. }
  895. }
  896. print(QString("Processed finished in %1 s.").arg(eTimer.elapsed() / 1.0e3, 0, 'f', 2));
  897. return success ? EXIT_SUCCESS : EXIT_FAILURE;
  898. }