| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970 |
- //CCCoreLib
- #include <AutoSegmentationTools.h>
- #include <CCConst.h>
- #include <CloudSamplingTools.h>
- #include <MeshSamplingTools.h>
- #include <NormalDistribution.h>
- #include <StatisticalTestingTools.h>
- #include <WeibullDistribution.h>
- #include <DistanceComputationTools.h>
- //qCC_db
- #include <ccHObjectCaster.h>
- #include <ccNormalVectors.h>
- #include <ccPlane.h>
- #include <ccPolyline.h>
- #include <ccProgressDialog.h>
- #include <ccScalarField.h>
- #include <ccVolumeCalcTool.h>
- #include <ccSubMesh.h>
- #include <ccPointCloudInterpolator.h>
- //qCC_io
- #include <AsciiFilter.h>
- #include <PlyFilter.h>
- //qCC
- #include "ccCommon.h"
- #include "ccComparisonDlg.h"
- #include "ccConsole.h"
- #include "ccCropTool.h"
- #include "ccLibAlgorithms.h"
- #include "ccRegistrationTools.h"
- #include "ccScalarFieldArithmeticsDlg.h"
- #include "ccColorLevelsDlg.h"
- //Qt
- #include "ccCommandLineCommands.h"
- //Local
- #include "ccEntityAction.h"
- #include <QDateTime>
- #include <QFileInfo>
- //commands
- constexpr char COMMAND_CLOUD_EXPORT_FORMAT[] = "C_EXPORT_FMT";
- constexpr char COMMAND_EXPORT_EXTENSION[] = "EXT";
- constexpr char COMMAND_ASCII_EXPORT_PRECISION[] = "PREC";
- constexpr char COMMAND_ASCII_EXPORT_SEPARATOR[] = "SEP";
- constexpr char COMMAND_ASCII_EXPORT_ADD_COL_HEADER[] = "ADD_HEADER";
- constexpr char COMMAND_ASCII_EXPORT_ADD_PTS_COUNT[] = "ADD_PTS_COUNT";
- constexpr char COMMAND_MESH_EXPORT_FORMAT[] = "M_EXPORT_FMT";
- constexpr char COMMAND_HIERARCHY_EXPORT_FORMAT[] = "H_EXPORT_FMT";
- constexpr char COMMAND_OPEN[] = "O"; //+ file name
- constexpr char COMMAND_OPEN_SKIP_LINES[] = "SKIP"; //+ number of lines to skip
- constexpr char COMMAND_OPEN_NO_LABEL[] = "NO_LABEL";
- constexpr char COMMAND_COMMAND_FILE[] = "COMMAND_FILE"; //+ file name
- constexpr char COMMAND_SUBSAMPLE[] = "SS"; //+ method (RANDOM/SPATIAL/OCTREE) + parameter (resp. point count / spatial step / octree level)
- constexpr char COMMAND_EXTRACT_CC[] = "EXTRACT_CC";
- constexpr char COMMAND_CURVATURE[] = "CURV"; //+ curvature type (MEAN/GAUSS)
- constexpr char COMMAND_DENSITY[] = "DENSITY"; //+ sphere radius
- constexpr char COMMAND_DENSITY_TYPE[] = "TYPE"; //+ density type
- constexpr char COMMAND_APPROX_DENSITY[] = "APPROX_DENSITY";
- constexpr char COMMAND_SF_GRADIENT[] = "SF_GRAD";
- constexpr char COMMAND_ROUGHNESS[] = "ROUGH";
- constexpr char COMMAND_ROUGHNESS_UP_DIR[] = "UP_DIR";
- constexpr char COMMAND_APPLY_TRANSFORMATION[] = "APPLY_TRANS";
- constexpr char COMMAND_APPLY_TRANS_TO_GLOBAL[] = "APPLY_TO_GLOBAL";
- constexpr char COMMAND_APPLY_TRANS_INVERSE[] = "INVERSE";
- constexpr char COMMAND_DROP_GLOBAL_SHIFT[] = "DROP_GLOBAL_SHIFT";
- constexpr char COMMAND_SF_COLOR_SCALE[] = "SF_COLOR_SCALE";
- constexpr char COMMAND_SF_CONVERT_TO_RGB[] = "SF_CONVERT_TO_RGB";
- constexpr char COMMAND_FILTER_SF_BY_VALUE[] = "FILTER_SF";
- constexpr char COMMAND_MERGE_CLOUDS[] = "MERGE_CLOUDS";
- constexpr char COMMAND_MERGE_MESHES[] = "MERGE_MESHES";
- constexpr char COMMAND_SET_ACTIVE_SF[] = "SET_ACTIVE_SF";
- constexpr char COMMAND_SET_GLOBAL_SHIFT[] = "SET_GLOBAL_SHIFT"; // + global shift {x,y,z}
- constexpr char COMMAND_SET_GLOBAL_SHIFT_KEEP_ORIG_FIXED[]= "KEEP_ORIG_FIXED";
- constexpr char COMMAND_REMOVE_ALL_SFS[] = "REMOVE_ALL_SFS";
- constexpr char COMMAND_REMOVE_SF[] = "REMOVE_SF";
- constexpr char COMMAND_REMOVE_SCAN_GRIDS[] = "REMOVE_SCAN_GRIDS";
- constexpr char COMMAND_REMOVE_SENSORS[] = "REMOVE_SENSORS";
- constexpr char COMMAND_REMOVE_RGB[] = "REMOVE_RGB";
- constexpr char COMMAND_REMOVE_NORMALS[] = "REMOVE_NORMALS";
- constexpr char COMMAND_MATCH_BB_CENTERS[] = "MATCH_CENTERS";
- constexpr char COMMAND_BEST_FIT_PLANE[] = "BEST_FIT_PLANE";
- constexpr char COMMAND_BEST_FIT_PLANE_MAKE_HORIZ[] = "MAKE_HORIZ";
- constexpr char COMMAND_BEST_FIT_PLANE_KEEP_LOADED[] = "KEEP_LOADED";
- constexpr char COMMAND_ORIENT_NORMALS[] = "ORIENT_NORMS_MST";
- constexpr char COMMAND_SOR_FILTER[] = "SOR";
- constexpr char COMMAND_NOISE_FILTER[] = "NOISE";
- constexpr char COMMAND_NOISE_FILTER_KNN[] = "KNN";
- constexpr char COMMAND_NOISE_FILTER_RADIUS[] = "RADIUS";
- constexpr char COMMAND_NOISE_FILTER_REL[] = "REL";
- constexpr char COMMAND_NOISE_FILTER_ABS[] = "ABS";
- constexpr char COMMAND_NOISE_FILTER_RIP[] = "RIP";
- constexpr char COMMAND_REMOVE_DUPLICATE_POINTS[] = "RDP";
- constexpr char COMMAND_SAMPLE_MESH[] = "SAMPLE_MESH";
- constexpr char COMMAND_COMPRESS_FWF[] = "COMPRESS_FWF";
- constexpr char COMMAND_CROP[] = "CROP";
- constexpr char COMMAND_CROP_OUTSIDE[] = "OUTSIDE";
- constexpr char COMMAND_CROP_2D[] = "CROP2D";
- constexpr char COMMAND_COLOR_BANDING[] = "CBANDING";
- constexpr char COMMAND_COLOR_LEVELS[] = "CLEVELS";
- constexpr char COMMAND_C2M_DIST[] = "C2M_DIST";
- constexpr char COMMAND_C2M_DIST_FLIP_NORMALS[] = "FLIP_NORMS";
- constexpr char COMMAND_C2M_DIST_UNSIGNED[] = "UNSIGNED";
- constexpr char COMMAND_C2M_DIST_NON_ROBUST[] = "NON_ROBUST";
- constexpr char COMMAND_C2M_NORMAL_MATCHING[] = "NORMAL_MATCH";
- constexpr char COMMAND_C2C_DIST[] = "C2C_DIST";
- constexpr char COMMAND_CLOSEST_POINT_SET[] = "CLOSEST_POINT_SET";
- constexpr char COMMAND_C2C_SPLIT_XYZ[] = "SPLIT_XYZ";
- constexpr char COMMAND_C2C_SPLIT_XY_Z[] = "SPLIT_XY_Z";
- constexpr char COMMAND_C2C_LOCAL_MODEL[] = "MODEL";
- constexpr char COMMAND_C2X_MAX_DISTANCE[] = "MAX_DIST";
- constexpr char COMMAND_C2X_OCTREE_LEVEL[] = "OCTREE_LEVEL";
- constexpr char COMMAND_STAT_TEST[] = "STAT_TEST";
- constexpr char COMMAND_DELAUNAY[] = "DELAUNAY";
- constexpr char COMMAND_DELAUNAY_AA[] = "AA";
- constexpr char COMMAND_DELAUNAY_BF[] = "BEST_FIT";
- constexpr char COMMAND_DELAUNAY_MAX_EDGE_LENGTH[] = "MAX_EDGE_LENGTH";
- constexpr char COMMAND_SF_ARITHMETIC[] = "SF_ARITHMETIC";
- constexpr char COMMAND_SF_ARITHMETIC_IN_PLACE[] = "IN_PLACE";
- constexpr char COMMAND_SF_OP[] = "SF_OP";
- constexpr char COMMAND_SF_OP_NOT_IN_PLACE[] = "NOT_IN_PLACE";
- constexpr char COMMAND_SF_OP_SF[] = "SF_OP_SF";
- constexpr char COMMAND_SF_INTERP[] = "SF_INTERP";
- constexpr char COMMAND_COLOR_INTERP[] = "COLOR_INTERP";
- constexpr char COMMAND_SF_INTERP_DEST_IS_FIRST[] = "DEST_IS_FIRST";
- constexpr char COMMAND_SF_ADD_CONST[] = "SF_ADD_CONST";
- constexpr char COMMAND_SF_ADD_ID[] = "SF_ADD_ID";
- constexpr char COMMAND_SF_ADD_ID_AS_INT[] = "AS_INT";
- constexpr char COMMAND_RENAME_ENTITIES[] = "RENAME_ENTITIES"; //+ base name
- constexpr char COMMAND_RENAME_SF[] = "RENAME_SF";
- constexpr char COMMAND_COORD_TO_SF[] = "COORD_TO_SF";
- constexpr char COMMAND_SF_TO_COORD[] = "SF_TO_COORD";
- constexpr char COMMAND_EXTRACT_VERTICES[] = "EXTRACT_VERTICES";
- constexpr char COMMAND_ICP[] = "ICP";
- constexpr char COMMAND_ICP_REFERENCE_IS_FIRST[] = "REFERENCE_IS_FIRST";
- constexpr char COMMAND_ICP_MIN_ERROR_DIIF[] = "MIN_ERROR_DIFF";
- constexpr char COMMAND_ICP_ITERATION_COUNT[] = "ITER";
- constexpr char COMMAND_ICP_OVERLAP[] = "OVERLAP";
- constexpr char COMMAND_ICP_ADJUST_SCALE[] = "ADJUST_SCALE";
- constexpr char COMMAND_ICP_RANDOM_SAMPLING_LIMIT[] = "RANDOM_SAMPLING_LIMIT";
- constexpr char COMMAND_ICP_ENABLE_FARTHEST_REMOVAL[] = "FARTHEST_REMOVAL";
- constexpr char COMMAND_ICP_USE_MODEL_SF_AS_WEIGHT[] = "MODEL_SF_AS_WEIGHTS";
- constexpr char COMMAND_ICP_USE_DATA_SF_AS_WEIGHT[] = "DATA_SF_AS_WEIGHTS";
- constexpr char COMMAND_ICP_ROT[] = "ROT";
- constexpr char COMMAND_ICP_SKIP_TX[] = "SKIP_TX";
- constexpr char COMMAND_ICP_SKIP_TY[] = "SKIP_TY";
- constexpr char COMMAND_ICP_SKIP_TZ[] = "SKIP_TZ";
- constexpr char COMMAND_ICP_C2M_DIST[] = "USE_C2M_DIST";
- constexpr char COMMAND_PLY_EXPORT_FORMAT[] = "PLY_EXPORT_FMT";
- constexpr char COMMAND_COMPUTE_GRIDDED_NORMALS[] = "COMPUTE_NORMALS";
- constexpr char COMMAND_INVERT_NORMALS[] = "INVERT_NORMALS";
- constexpr char COMMAND_COMPUTE_OCTREE_NORMALS[] = "OCTREE_NORMALS";
- constexpr char COMMAND_CONVERT_NORMALS_TO_DIP[] = "NORMALS_TO_DIP";
- constexpr char COMMAND_CONVERT_NORMALS_TO_SFS[] = "NORMALS_TO_SFS";
- constexpr char COMMAND_CONVERT_NORMALS_TO_HSV[] = "NORMALS_TO_HSV";
- constexpr char COMMAND_CLEAR_NORMALS[] = "CLEAR_NORMALS";
- constexpr char COMMAND_MESH_VOLUME[] = "MESH_VOLUME";
- constexpr char COMMAND_VOLUME_TO_FILE[] = "TO_FILE";
- constexpr char COMMAND_SAVE_CLOUDS[] = "SAVE_CLOUDS";
- constexpr char COMMAND_SAVE_MESHES[] = "SAVE_MESHES";
- constexpr char COMMAND_AUTO_SAVE[] = "AUTO_SAVE";
- constexpr char COMMAND_LOG_FILE[] = "LOG_FILE";
- constexpr char COMMAND_SELECT_ENTITIES[] = "SELECT_ENTITIES";
- constexpr char COMMAND_CLEAR[] = "CLEAR";
- constexpr char COMMAND_CLEAR_CLOUDS[] = "CLEAR_CLOUDS";
- constexpr char COMMAND_POP_CLOUDS[] = "POP_CLOUDS";
- constexpr char COMMAND_CLEAR_MESHES[] = "CLEAR_MESHES";
- constexpr char COMMAND_POP_MESHES[] = "POP_MESHES";
- constexpr char COMMAND_NO_TIMESTAMP[] = "NO_TIMESTAMP";
- constexpr char COMMAND_MOMENT[] = "MOMENT";
- constexpr char COMMAND_FEATURE[] = "FEATURE";
- constexpr char COMMAND_RGB_CONVERT_TO_SF[] = "RGB_CONVERT_TO_SF";
- constexpr char COMMAND_FLIP_TRIANGLES[] = "FLIP_TRI";
- constexpr char COMMAND_DEBUG[] = "DEBUG";
- constexpr char COMMAND_VERBOSITY[] = "VERBOSITY";
- constexpr char COMMAND_FILTER[] = "FILTER";
- //options / modifiers
- constexpr char COMMAND_MAX_THREAD_COUNT[] = "MAX_TCOUNT";
- constexpr char OPTION_ALL_AT_ONCE[] = "ALL_AT_ONCE";
- constexpr char OPTION_ON[] = "ON";
- constexpr char OPTION_OFF[] = "OFF";
- constexpr char OPTION_FILE_NAMES[] = "FILE";
- constexpr char OPTION_ORIENT[] = "ORIENT";
- constexpr char OPTION_MODEL[] = "MODEL";
- constexpr char OPTION_FIRST[] = "FIRST";
- constexpr char OPTION_LAST[] = "LAST";
- constexpr char OPTION_ALL[] = "ALL";
- constexpr char OPTION_REGEX[] = "REGEX";
- constexpr char OPTION_NOT[] = "NOT";
- constexpr char OPTION_CLOUD[] = "CLOUD";
- constexpr char OPTION_MESH[] = "MESH";
- constexpr char OPTION_PERCENT[] = "PERCENT";
- constexpr char OPTION_NUMBER_OF_POINTS[] = "NUMBER_OF_POINTS";
- constexpr char OPTION_FORCE[] = "FORCE";
- constexpr char OPTION_USE_ACTIVE_SF[] = "USE_ACTIVE_SF";
- constexpr char OPTION_SF[] = "SF";
- constexpr char OPTION_RGB[] = "RGB";
- constexpr char OPTION_GAUSSIAN[] = "GAUSSIAN";
- constexpr char OPTION_BILATERAL[] = "BILATERAL";
- constexpr char OPTION_MEAN[] = "MEAN";
- constexpr char OPTION_MEDIAN[] = "MEDIAN";
- constexpr char OPTION_SIGMA[] = "SIGMA";
- constexpr char OPTION_SIGMA_SF[] = "SIGMA_SF";
- constexpr char OPTION_BURNT_COLOR_THRESHOLD[] = "BURNT_COLOR_THRESHOLD";
- constexpr char OPTION_BLEND_GRAYSCALE[] = "BLEND_GRAYSCALE";
- static bool GetSFIndexOrName(ccCommandLineInterface& cmd, int& sfIndex, QString& sfName, bool allowMinusOne = false)
- {
- sfName = cmd.arguments().takeFirst();
- if (sfName.toUpper() == OPTION_LAST)
- {
- sfIndex = -2;
- cmd.print(QObject::tr("SF index: LAST"));
- }
- else
- {
- bool validInt = false;
- sfIndex = sfName.toInt(&validInt);
- if (validInt)
- {
- sfName.clear(); //user has provided an index, not a name
- if (sfIndex < 0)
- {
- if (allowMinusOne && sfIndex == -1)
- {
- // -1 means 'no active SF'
- cmd.print(QObject::tr("SF index: none"));
- }
- else
- {
- // invalid index
- cmd.warning(QObject::tr("Invalid SF index: %1").arg(sfIndex));
- return false;
- }
- }
- else
- {
- cmd.print(QObject::tr("SF index: %1").arg(sfIndex));
- }
- }
- else
- {
- cmd.print(QObject::tr("SF name: '%1'").arg(sfName));
- sfIndex = -1;
- }
- }
- return true;
- }
- int GetScalarFieldIndex(ccPointCloud* cloud, int sfIndex, const QString& sfName, bool minusOneMeansCurrent = false)
- {
- if (!cloud)
- {
- assert(false);
- return -1;
- }
- else if (!cloud->hasScalarFields())
- {
- return -1;
- }
- else if (sfIndex == -2)
- {
- return static_cast<int>(cloud->getNumberOfScalarFields()) - 1;
- }
- else if (sfIndex == -1)
- {
- if (!sfName.isEmpty()) // the user has provided a SF name instead of an index
- {
- //check if this cloud has a scalar field with the input name
- sfIndex = cloud->getScalarFieldIndexByName(sfName.toStdString());
- if (sfIndex < 0)
- {
- ccLog::Warning(QObject::tr("Cloud %1 has no SF named '%2'").arg(cloud->getName()).arg(sfName));
- return -1;
- }
- }
- else if (minusOneMeansCurrent)
- {
- return cloud->getCurrentInScalarFieldIndex();
- }
- else
- {
- ccLog::Warning(QObject::tr("Input scalar field index is invalid: %1").arg(sfIndex));
- return -1;
- }
- }
- else if (sfIndex >= static_cast<int>(cloud->getNumberOfScalarFields()))
- {
- ccLog::Warning(QObject::tr("Cloud %1 has less scalar fields than the SF index (%2/%3)").arg(cloud->getName()).arg(sfIndex).arg(cloud->getNumberOfScalarFields()));
- return -1;
- }
- return sfIndex;
- }
- CCCoreLib::ScalarField* GetScalarField(ccPointCloud* cloud, int sfIndex, const QString& sfName, bool minusOneMeansCurrent = false)
- {
- sfIndex = GetScalarFieldIndex(cloud, sfIndex, sfName, minusOneMeansCurrent);
- if (sfIndex < 0)
- {
- return nullptr;
- }
- else
- {
- return cloud->getScalarField(sfIndex);
- }
- }
- CommandChangeOutputFormat::CommandChangeOutputFormat(const QString& name, const QString& keyword)
- : ccCommandLineInterface::Command(name, keyword)
- {}
- QString CommandChangeOutputFormat::getFileFormatFilter(ccCommandLineInterface& cmd, QString& defaultExt)
- {
- QString fileFilter;
- defaultExt.clear();
- if (!cmd.arguments().isEmpty())
- {
- //test if the specified format corresponds to a known file type
- QString extension = cmd.arguments().front();
- QString upperExtension = extension.toUpper();
- cmd.arguments().pop_front();
- const FileIOFilter::FilterContainer& filters = FileIOFilter::GetFilters();
-
- for (const auto& filter : filters)
- {
- if (upperExtension == filter->getDefaultExtension().toUpper())
- {
- //found a matching filter
- fileFilter = filter->getFileFilters(false).first();
- defaultExt = filter->getDefaultExtension();
- break;
- }
- }
- //haven't found anything?
- if (fileFilter.isEmpty())
- {
- ccLog::Warning(QString("Extension '%1' was not identified as a primary extension. Let's look for secondary ones...").arg(extension));
- // let's look for secondary formats
- for (const auto& filter : filters)
- {
- QStringList outputFilters = filter->getFileFilters(false);
- for (const QString& outputFilter : outputFilters)
- {
- int index = outputFilter.toUpper().indexOf(upperExtension);
- if (index >= 2 && outputFilter.mid(index - 2, 2) == "*.")
- {
- ccLog::Print(QString("Extension '%1' found in the output formats supported by the '%2' filter").arg(extension).arg(filter->getDefaultExtension().toUpper()));
- fileFilter = outputFilter;
- defaultExt = extension;
- break;
- }
- }
- if (!fileFilter.isEmpty())
- {
- break;
- }
- }
- }
- //still haven't found anything?
- if (fileFilter.isEmpty())
- {
- cmd.error(QObject::tr("Unhandled format specifier (%1)").arg(extension));
- }
- }
- else
- {
- cmd.error(QObject::tr("Missing file format specifier!"));
- }
- return fileFilter;
- }
- CommandChangeCloudOutputFormat::CommandChangeCloudOutputFormat()
- : CommandChangeOutputFormat(QObject::tr("Change cloud output format"), COMMAND_CLOUD_EXPORT_FORMAT)
- {}
- bool CommandChangeCloudOutputFormat::process(ccCommandLineInterface& cmd)
- {
- QString defaultExt;
- QString fileFilter = getFileFormatFilter(cmd, defaultExt);
- if (fileFilter.isEmpty())
- {
- return false;
- }
-
- cmd.setCloudExportFormat(fileFilter, defaultExt);
- cmd.print(QObject::tr("Output export format (clouds) set to: %1").arg(defaultExt.toUpper()));
-
- //default options for ASCII output
- if (fileFilter == AsciiFilter::GetFileFilter())
- {
- AsciiFilter::SetOutputCoordsPrecision(cmd.numericalPrecision());
- AsciiFilter::SetOutputSFPrecision(cmd.numericalPrecision());
- AsciiFilter::SetOutputSeparatorIndex(0); //space
- AsciiFilter::SaveSFBeforeColor(false); //default order: point, color, SF, normal
- AsciiFilter::SaveColumnsNamesHeader(false);
- AsciiFilter::SavePointCountHeader(false);
- }
-
- //look for additional parameters
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_EXPORT_EXTENSION))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: extension after '%1'").arg(COMMAND_EXPORT_EXTENSION));
- }
-
- cmd.setCloudExportFormat(cmd.cloudExportFormat(), cmd.arguments().takeFirst());
- cmd.print(QObject::tr("New output extension for clouds: %1").arg(cmd.cloudExportExt()));
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ASCII_EXPORT_PRECISION))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: precision value after '%1'").arg(COMMAND_ASCII_EXPORT_PRECISION));
- }
- bool ok;
- int precision = cmd.arguments().takeFirst().toInt(&ok);
- if (!ok || precision < 0)
- {
- return cmd.error(QObject::tr("Invalid value for precision! (%1)").arg(COMMAND_ASCII_EXPORT_PRECISION));
- }
-
- if (fileFilter != AsciiFilter::GetFileFilter())
- {
- cmd.warning(QObject::tr("Argument '%1' is only applicable to ASCII format!").arg(argument));
- }
-
- AsciiFilter::SetOutputCoordsPrecision(precision);
- AsciiFilter::SetOutputSFPrecision(precision);
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ASCII_EXPORT_SEPARATOR))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: separator character after '%1'").arg(COMMAND_ASCII_EXPORT_SEPARATOR));
- }
-
- if (fileFilter != AsciiFilter::GetFileFilter())
- {
- cmd.warning(QObject::tr("Argument '%1' is only applicable to ASCII format!").arg(argument));
- }
-
- QString separatorStr = cmd.arguments().takeFirst().toUpper();
- //printf("%s\n",qPrintable(separatorStr));
- int index = -1;
- if (separatorStr == "SPACE")
- {
- index = 0;
- }
- else if (separatorStr == "SEMICOLON")
- {
- index = 1;
- }
- else if (separatorStr == "COMMA")
- {
- index = 2;
- }
- else if (separatorStr == "TAB")
- {
- index = 3;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid separator! ('%1')").arg(separatorStr));
- }
-
- AsciiFilter::SetOutputSeparatorIndex(index);
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ASCII_EXPORT_ADD_COL_HEADER))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (fileFilter != AsciiFilter::GetFileFilter())
- {
- cmd.warning(QObject::tr("Argument '%1' is only applicable to ASCII format!").arg(argument));
- }
-
- AsciiFilter::SaveColumnsNamesHeader(true);
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ASCII_EXPORT_ADD_PTS_COUNT))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (fileFilter != AsciiFilter::GetFileFilter())
- {
- cmd.warning(QObject::tr("Argument '%1' is only applicable to ASCII format!").arg(argument));
- }
-
- AsciiFilter::SavePointCountHeader(true);
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
-
- return true;
- }
- CommandChangeMeshOutputFormat::CommandChangeMeshOutputFormat()
- : CommandChangeOutputFormat(QObject::tr("Change mesh output format"), COMMAND_MESH_EXPORT_FORMAT)
- {}
- bool CommandChangeMeshOutputFormat::process(ccCommandLineInterface& cmd)
- {
- QString defaultExt;
- QString fileFilter = getFileFormatFilter(cmd, defaultExt);
- if (fileFilter.isEmpty())
- {
- return false;
- }
-
- cmd.setMeshExportFormat(fileFilter, defaultExt);
- cmd.print(QObject::tr("Output export format (meshes) set to: %1").arg(defaultExt.toUpper()));
-
- //look for additional parameters
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
-
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_EXPORT_EXTENSION))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: extension after '%1'").arg(COMMAND_EXPORT_EXTENSION));
- }
-
- cmd.setMeshExportFormat(cmd.meshExportFormat(), cmd.arguments().takeFirst());
- cmd.print(QObject::tr("New output extension for meshes: %1").arg(cmd.meshExportExt()));
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
-
- return true;
- }
- CommandChangeHierarchyOutputFormat::CommandChangeHierarchyOutputFormat()
- : CommandChangeOutputFormat(QObject::tr("Change hierarchy output format"), COMMAND_HIERARCHY_EXPORT_FORMAT)
- {}
- bool CommandChangeHierarchyOutputFormat::process(ccCommandLineInterface& cmd)
- {
- QString defaultExt;
- QString fileFilter = getFileFormatFilter(cmd, defaultExt);
- if (fileFilter.isEmpty())
- {
- return false;
- }
- cmd.setHierarchyExportFormat(fileFilter, defaultExt);
- cmd.print(QObject::tr("Output export format (hierarchy) set to: %1").arg(defaultExt.toUpper()));
- //look for additional parameters
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_EXPORT_EXTENSION))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: extension after '%1'").arg(COMMAND_EXPORT_EXTENSION));
- }
- cmd.setHierarchyExportFormat(cmd.hierarchyExportFormat(), cmd.arguments().takeFirst());
- cmd.print(QObject::tr("New output extension for hierarchies: %1").arg(cmd.hierarchyExportExt()));
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
- return true;
- }
- CommandLoad::CommandLoad()
- : ccCommandLineInterface::Command(QObject::tr("Load"), COMMAND_OPEN)
- {}
- bool CommandLoad::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: filename after \"-%1\"").arg(COMMAND_OPEN));
- }
-
- //optional parameters
- int skipLines = 0;
- ccCommandLineInterface::GlobalShiftOptions globalShiftOptions;
- bool doNotCreateLabels = false;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_OPEN_NO_LABEL))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- cmd.print(QObject::tr("Will not load labels"));
- doNotCreateLabels = true;
- }
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_OPEN_SKIP_LINES))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number of lines after '%1'").arg(COMMAND_OPEN_SKIP_LINES));
- }
-
- bool ok;
- skipLines = cmd.arguments().takeFirst().toInt(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid parameter: number of lines after '%1'").arg(COMMAND_OPEN_SKIP_LINES));
- }
-
- cmd.print(QObject::tr("Will skip %1 lines").arg(skipLines));
- }
- else if (cmd.nextCommandIsGlobalShift())
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (!cmd.processGlobalShiftCommand(globalShiftOptions))
- {
- //error message already issued
- return false;
- }
- }
- else
- {
- break;
- }
- }
-
- if (skipLines >= 0)
- {
- AsciiFilter::SetDefaultSkippedLineCount(skipLines);
- }
- AsciiFilter::SetNoLabelCreated(doNotCreateLabels);
-
- //open specified file
- QString filename(cmd.arguments().takeFirst());
- if (!cmd.importFile(filename, globalShiftOptions))
- {
- return false;
- }
- return true;
- }
- CommandLoadCommandFile::CommandLoadCommandFile()
- : ccCommandLineInterface::Command(QObject::tr("Load commands from file"), COMMAND_COMMAND_FILE)
- {}
- bool CommandLoadCommandFile::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: filename after \"-%1\"").arg(COMMAND_COMMAND_FILE));
- }
- QString commandFilePath = cmd.arguments().takeFirst();
- //check if file exists
- if (!QFileInfo::exists(commandFilePath))
- {
- return cmd.error(QObject::tr("Command file not exists \"-%1\"").arg(commandFilePath));
- }
- QFile commandFile(commandFilePath);
- if (commandFile.open(QIODevice::ReadOnly))
- {
- int insertingIndex = 0;
- QTextStream in(&commandFile);
- while (!in.atEnd())
- {
- QString line = in.readLine().trimmed();
- //early abort whole line comments
- if (line.startsWith("#") || line.startsWith("//"))
- {
- cmd.print(QObject::tr("\t[COMMENT] %1").arg(line));
- continue;
- }
- QStringList argumentsInLine = line.split(" ");
- QStringList processedArgs;
- // 'massage' the arguments to handle single/double quotes
- //TODO handle escaped quotes/spaces
- {
- bool insideSingleQuoteSection = false;
- bool insideDoubleQuoteSection = false;
- QString buffer;
- static const QChar SingleQuote{ '\'' };
- static const QChar DoubleQuote{ '"' };
- for (int currentArgIndex = 0; currentArgIndex < argumentsInLine.size(); ++currentArgIndex)
- {
- QString arg = argumentsInLine[currentArgIndex];
- //handle singleQuotes
- {
- // argument starts with a single quote and not inside double quotes
- if (!insideSingleQuoteSection && !insideDoubleQuoteSection && arg.startsWith(SingleQuote))
- {
- if (arg.endsWith(SingleQuote))
- {
- arg.remove(SingleQuote);
- // nothing to do, non*truncated argument
- }
- else
- {
- // we'll collect the next pieces to get the full argument
- insideSingleQuoteSection = true;
- buffer = arg.mid(1); // remove the single quote
- }
- }
- else if (insideSingleQuoteSection)
- {
- buffer += QChar(' ') + arg; // append the current argument to the previous one(s)
- if (arg.endsWith(SingleQuote))
- {
- insideSingleQuoteSection = false;
- arg = buffer.left(buffer.length() - 1); // remove the single quote
- arg.remove(SingleQuote);
- }
- }
- }
- //handle doubleQuotes
- {
- // argument starts with a double quote and not inside single quotes
- if (!insideSingleQuoteSection && !insideDoubleQuoteSection && arg.startsWith(DoubleQuote))
- {
- if (arg.endsWith(DoubleQuote))
- {
- arg.remove(DoubleQuote);
- // nothing to do, non*truncated argument
- }
- else
- {
- // we'll collect the next pieces to get the full argument
- insideDoubleQuoteSection = true;
- buffer = arg.mid(1); // remove the double quote
- }
- }
- else if (insideDoubleQuoteSection)
- {
- buffer += QChar(' ') + arg; // append the current argument to the previous one(s)
- if (arg.endsWith(DoubleQuote))
- {
- insideDoubleQuoteSection = false;
- arg = buffer.left(buffer.length() - 1); // remove the double quote
- arg.remove(DoubleQuote);
- }
- }
- }
- if (!insideSingleQuoteSection && !insideDoubleQuoteSection)
- {
- processedArgs.append(arg);
- }
- }
- if (insideSingleQuoteSection || insideDoubleQuoteSection)
- {
- // the single/double quote section was not closed...
- cmd.warning("Probably malformed command (missing closing quote)");
- // ...still, we'll try to proceed
- processedArgs.append(buffer);
- }
- }
- //inject back all the arguments to the cmd.arguments()
- while (!processedArgs.isEmpty())
- {
- QString processedArg = processedArgs.takeFirst();
- if (!processedArg.isEmpty())
- {
- if (processedArg.startsWith("//") || processedArg.startsWith("#"))
- {
- //abort and keep the rest in processedArgs
- break;
- }
- if (!(processedArg.startsWith("/*") && processedArg.endsWith("*/")))
- {
- //standard argument
- cmd.arguments().insert(insertingIndex, processedArg);
- insertingIndex++;
- cmd.print(QObject::tr("\t[%1] %2").arg(insertingIndex - 1).arg(processedArg));
- }
- else
- {
- cmd.print(QObject::tr("\t[COMMENT] %1").arg(processedArg));
- }
- }
- }
- //if any arguments left, then it is a comment
- if (!processedArgs.isEmpty())
- {
- cmd.print(QObject::tr("\t[COMMENT] %1").arg(processedArgs.join(" ")));
- }
- }
- commandFile.close();
- }
- return true;
- }
- CommandClearNormals::CommandClearNormals()
- : ccCommandLineInterface::Command(QObject::tr("Clears normals"), COMMAND_CLEAR_NORMALS)
- {}
- bool CommandClearNormals::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No entity loaded (be sure to open at least one file with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_CLEAR_NORMALS));
- }
-
- for (const CLCloudDesc& thisCloudDesc : cmd.clouds())
- {
- ccPointCloud* cloud = thisCloudDesc.pc;
- if (cloud)
- {
- cloud->unallocateNorms();
- }
- }
-
- for (const CLMeshDesc& thisMeshDesc : cmd.meshes())
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(thisMeshDesc.mesh);
- if (!mesh)
- {
- assert(false);
- continue;
- }
-
- mesh->clearTriNormals();
-
- if (mesh->getParent()
- && (mesh->getParent()->isA(CC_TYPES::MESH)/*|| mesh->getParent()->isKindOf(CC_TYPES::PRIMITIVE)*/) //TODO
- && ccHObjectCaster::ToMesh(mesh->getParent())->getAssociatedCloud() == mesh->getAssociatedCloud())
- {
- //Can't remove per-vertex normals on a sub mesh!
- }
- else
- {
- //mesh is alone, we can freely remove normals
- if (mesh->getAssociatedCloud() && mesh->getAssociatedCloud()->isA(CC_TYPES::POINT_CLOUD))
- {
- static_cast<ccPointCloud*>(mesh->getAssociatedCloud())->unallocateNorms();
- }
- }
- }
-
- return true;
- }
- CommandInvertNormal::CommandInvertNormal()
- : ccCommandLineInterface::Command(QObject::tr("Invert normals"), COMMAND_INVERT_NORMALS)
- {}
- bool CommandInvertNormal::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No input point cloud or mesh (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_INVERT_NORMALS));
- }
- for (CLCloudDesc& thisCloudDesc : cmd.clouds())
- {
- ccPointCloud* cloud = thisCloudDesc.pc;
- if (!cloud->hasNormals())
- {
- cmd.warning(QObject::tr("Cloud %1 has no normals").arg(cloud->getName()));
- continue;
- }
- cloud->invertNormals();
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(thisCloudDesc, "_INVERTED_NORMALS");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- for (CLMeshDesc& thisMeshDesc : cmd.meshes())
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(thisMeshDesc.mesh);
- if (!mesh)
- {
- assert(false);
- continue;
- }
- if (!mesh->hasNormals())
- {
- cmd.warning(QObject::tr("Mesh %1 has no normals").arg(mesh->getName()));
- continue;
- }
- mesh->invertNormals();
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(thisMeshDesc, "_INVERTED_NORMALS");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- return true;
- }
- CommandOctreeNormal::CommandOctreeNormal()
- : ccCommandLineInterface::Command(QObject::tr("Compute normals with octree"), COMMAND_COMPUTE_OCTREE_NORMALS)
- {}
- bool CommandOctreeNormal::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud to compute normals (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_COMPUTE_OCTREE_NORMALS));
- }
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: radius after \"-%1\"").arg(COMMAND_COMPUTE_OCTREE_NORMALS));
- }
-
- float radius = std::numeric_limits<float>::quiet_NaN(); //if this stays
- QString radiusArg = cmd.arguments().takeFirst();
- if (radiusArg.toUpper() != "AUTO")
- {
- bool ok = false;
- radius = radiusArg.toFloat(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid radius"));
- }
- }
- cmd.print(QObject::tr("\tRadius: %1").arg(radiusArg));
- CCCoreLib::LOCAL_MODEL_TYPES model = CCCoreLib::QUADRIC;
- ccNormalVectors::Orientation orientation = ccNormalVectors::Orientation::UNDEFINED;
-
- while (!cmd.arguments().isEmpty())
- {
- QString argument = cmd.arguments().front().toUpper();
- if (ccCommandLineInterface::IsCommand(argument, OPTION_ORIENT))
- {
- cmd.arguments().takeFirst();
- if (!cmd.arguments().isEmpty())
- {
- QString orient_argument = cmd.arguments().takeFirst().toUpper();
- if (orient_argument == "PLUS_ZERO" || orient_argument == "PLUS_ORIGIN")
- {
- orientation = ccNormalVectors::Orientation::PLUS_ORIGIN;
- }
- else if (orient_argument == "MINUS_ZERO" || orient_argument == "MINUS_ORIGIN")
- {
- orientation = ccNormalVectors::Orientation::MINUS_ORIGIN;
- }
- else if (orient_argument == "PLUS_BARYCENTER")
- {
- orientation = ccNormalVectors::Orientation::PLUS_BARYCENTER;
- }
- else if (orient_argument == "MINUS_BARYCENTER")
- {
- orientation = ccNormalVectors::Orientation::MINUS_BARYCENTER;
- }
- else if (orient_argument == "PLUS_X")
- {
- orientation = ccNormalVectors::Orientation::PLUS_X;
- }
- else if (orient_argument == "MINUS_X")
- {
- orientation = ccNormalVectors::Orientation::MINUS_X;
- }
- else if (orient_argument == "PLUS_Y")
- {
- orientation = ccNormalVectors::Orientation::PLUS_Y;
- }
- else if (orient_argument == "MINUS_Y")
- {
- orientation = ccNormalVectors::Orientation::MINUS_Y;
- }
- else if (orient_argument == "PLUS_Z")
- {
- orientation = ccNormalVectors::Orientation::PLUS_Z;
- }
- else if (orient_argument == "MINUS_Z")
- {
- orientation = ccNormalVectors::Orientation::MINUS_Z;
- }
- else if (orient_argument == "PREVIOUS")
- {
- orientation = ccNormalVectors::Orientation::PREVIOUS;
- }
- else if (orient_argument == "PLUS_SENSOR_ORIGIN")
- {
- orientation = ccNormalVectors::Orientation::PLUS_SENSOR_ORIGIN;
- }
- else if (orient_argument == "MINUS_SENSOR_ORIGIN")
- {
- orientation = ccNormalVectors::Orientation::MINUS_SENSOR_ORIGIN;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: unknown orientation '%1'").arg(orient_argument));
- }
- }
- else
- {
- return cmd.error(QObject::tr("Missing orientation"));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_MODEL))
- {
- cmd.arguments().takeFirst();
- if (!cmd.arguments().isEmpty())
- {
- QString model_arg = cmd.arguments().takeFirst().toUpper();
- if (model_arg == "LS")
- {
- model = CCCoreLib::LOCAL_MODEL_TYPES::LS;
- }
- else if (model_arg == "TRI")
- {
- model = CCCoreLib::LOCAL_MODEL_TYPES::TRI;
- }
- else if (model_arg == "QUADRIC")
- {
- model = CCCoreLib::LOCAL_MODEL_TYPES::QUADRIC;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: unknown model '%1'").arg(model_arg));
- }
- }
- else
- {
- return cmd.error(QObject::tr("Missing model"));
- }
- }
- else
- {
- break;
- }
- }
-
- for (const CLCloudDesc& thisCloudDesc : cmd.clouds())
- {
- ccPointCloud* cloud = thisCloudDesc.pc;
- QScopedPointer<ccProgressDialog> progressDialog(nullptr);
- if (!cmd.silentMode())
- {
- progressDialog.reset(new ccProgressDialog(true, cmd.widgetParent()));
- progressDialog->setAutoClose(false);
- }
- if (!cloud->getOctree())
- {
- if (!cloud->computeOctree(progressDialog.data()))
- {
- return cmd.error(QObject::tr("Failed to compute octree for cloud '%1'").arg(cloud->getName()));
- }
- }
- float thisCloudRadius = radius;
- if (std::isnan(thisCloudRadius))
- {
- ccOctree::BestRadiusParams params;
- {
- params.aimedPopulationPerCell = 16;
- params.aimedPopulationRange = 4;
- params.minCellPopulation = 6;
- params.minAboveMinRatio = 0.97;
- }
- thisCloudRadius = ccOctree::GuessBestRadius(cloud, params, cloud->getOctree().data());
- if (thisCloudRadius == 0)
- {
- return cmd.error(QObject::tr("Failed to determine best normal radius for cloud '%1'").arg(cloud->getName()));
- }
- cmd.print(QObject::tr("\tCloud %1 radius = %2").arg(cloud->getName()).arg(thisCloudRadius));
- }
- cmd.print(QObject::tr("computeNormalsWithOctree started..."));
- bool success = cloud->computeNormalsWithOctree(model, orientation, thisCloudRadius, progressDialog.data());
- if(success)
- {
- cmd.print(QObject::tr("computeNormalsWithOctree success"));
- }
- else
- {
- return cmd.error(QObject::tr("computeNormalsWithOctree failed"));
- }
-
- cloud->setName(cloud->getName() + QObject::tr(".OctreeNormal"));
- if (cmd.autoSaveMode())
- {
- CLCloudDesc desc(cloud, thisCloudDesc.basename, thisCloudDesc.path, thisCloudDesc.indexInFile);
- QString errorStr = cmd.exportEntity(desc, "OCTREE_NORMALS");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
-
- return true;
- }
- CommandConvertNormalsToDipAndDipDir::CommandConvertNormalsToDipAndDipDir()
- : ccCommandLineInterface::Command(QObject::tr("Convert normals to dip and dip. dir."), COMMAND_CONVERT_NORMALS_TO_DIP)
- {}
- bool CommandConvertNormalsToDipAndDipDir::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No input point cloud (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_CONVERT_NORMALS_TO_DIP));
- }
- for (CLCloudDesc& thisCloudDesc : cmd.clouds())
- {
- ccPointCloud* cloud = thisCloudDesc.pc;
- if (!cloud->hasNormals())
- {
- cmd.warning(QObject::tr("Cloud %1 has no normals").arg(cloud->getName()));
- continue;
- }
- ccHObject::Container container;
- container.push_back(cloud);
- if (!ccEntityAction::convertNormalsTo(container, ccEntityAction::NORMAL_CONVERSION_DEST::DIP_DIR_SFS))
- {
- return cmd.error(QObject::tr("Failed to convert normals to dip and dip direction"));
- }
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(thisCloudDesc, "_DIP_AND_DIP_DIR");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- return true;
- }
- CommandConvertNormalsToSFs::CommandConvertNormalsToSFs()
- : ccCommandLineInterface::Command(QObject::tr("Convert normals to scalar fields"), COMMAND_CONVERT_NORMALS_TO_SFS)
- {}
- bool CommandConvertNormalsToSFs::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No input point cloud (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_CONVERT_NORMALS_TO_SFS));
- }
- for (CLCloudDesc& thisCloudDesc : cmd.clouds())
- {
- ccPointCloud* cloud = thisCloudDesc.pc;
- if (!cloud->hasNormals())
- {
- cmd.warning(QObject::tr("Cloud %1 has no normals").arg(cloud->getName()));
- continue;
- }
- bool exportDims[3] = { true, true, true };
- ccHObject::Container container;
- container.push_back(cloud);
- if (!ccEntityAction::exportNormalToSF(container, cmd.widgetParent(), exportDims))
- {
- return cmd.error(QObject::tr("Failed to convert normals to scalar fields"));
- }
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(thisCloudDesc, "_NORM_TO_SF");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- return true;
- }
- CommandConvertNormalsToHSV::CommandConvertNormalsToHSV()
- : ccCommandLineInterface::Command(QObject::tr("Convert normals to HSV colors"), COMMAND_CONVERT_NORMALS_TO_HSV)
- {}
- bool CommandConvertNormalsToHSV::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No input point cloud (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_CONVERT_NORMALS_TO_HSV));
- }
- for (CLCloudDesc& thisCloudDesc : cmd.clouds())
- {
- ccPointCloud* cloud = thisCloudDesc.pc;
- if (!cloud->hasNormals())
- {
- cmd.warning(QObject::tr("Cloud %1 has no normals").arg(cloud->getName()));
- continue;
- }
- ccHObject::Container container;
- container.push_back(cloud);
- if (!ccEntityAction::convertNormalsTo(container, ccEntityAction::NORMAL_CONVERSION_DEST::HSV_COLORS))
- {
- return cmd.error(QObject::tr("Failed to convert normals to HSV colors"));
- }
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(thisCloudDesc, "_HSV");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- return true;
- }
- CommandSubsample::CommandSubsample()
- : ccCommandLineInterface::Command(QObject::tr("Subsample"), COMMAND_SUBSAMPLE)
- {}
- bool CommandSubsample::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud to resample (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_SUBSAMPLE));
- }
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: resampling method after \"-%1\"").arg(COMMAND_SUBSAMPLE));
- }
-
- QString method = cmd.arguments().takeFirst().toUpper();
- cmd.print(QObject::tr("\tMethod: ") + method);
- if (method == "RANDOM")
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number of points or option \"%2\" after \"-%1 RANDOM \"").arg(COMMAND_SUBSAMPLE).arg(OPTION_PERCENT));
- }
- bool isPercent = false;
- double percent = 0.0;
- unsigned count = 0;
- //handle percent argument
- if (cmd.arguments().front() == OPTION_PERCENT)
- {
- //local option verified
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number after \"-%1 RANDOM %2\"").arg(COMMAND_SUBSAMPLE).arg(OPTION_PERCENT));
- }
- bool ok;
- percent = cmd.arguments().takeFirst().toDouble(&ok);
- if (!ok || percent < 0 || percent > 100)
- {
- return cmd.error(QObject::tr("Invalid parameter: number after \"-%1 RANDOM %2\" must be decimal between 0 and 100").arg(COMMAND_SUBSAMPLE).arg(OPTION_PERCENT));
- }
- isPercent = true;
- }
- else
- {
- bool ok;
- count = cmd.arguments().takeFirst().toUInt(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid parameter: number of points or option \"%2\" after \"-%1 RANDOM \"").arg(COMMAND_SUBSAMPLE).arg(OPTION_PERCENT));
- }
- cmd.print(QObject::tr("\tOutput points: %1").arg(count));
- }
-
- for (CLCloudDesc& desc : cmd.clouds())
- {
- cmd.print(QObject::tr("\tProcessing cloud %1").arg(!desc.pc->getName().isEmpty() ? desc.pc->getName() : "no name"));
- if (isPercent)
- {
- size_t nrOfPoints = desc.pc->size();
- count = static_cast<unsigned>(ceil(nrOfPoints * percent / 100));
- cmd.print(QObject::tr("\tOutput points: %1 * %2% = %3").arg(nrOfPoints).arg(percent).arg(count));
- }
- CCCoreLib::ReferenceCloud* refCloud = CCCoreLib::CloudSamplingTools::subsampleCloudRandomly(desc.pc, count, cmd.progressDialog());
- if (!refCloud)
- {
- return cmd.error(QObject::tr("Subsampling process failed!"));
- }
- cmd.print(QObject::tr("\tResult: %1 points").arg(refCloud->size()));
-
- //save output
- ccPointCloud* result = desc.pc->partialClone(refCloud);
- delete refCloud;
- refCloud = nullptr;
-
- if (result)
- {
- result->setName(desc.pc->getName() + QObject::tr(".subsampled"));
- if (cmd.autoSaveMode())
- {
- CLCloudDesc newDesc(result, desc.basename, desc.path, desc.indexInFile);
- QString errorStr = cmd.exportEntity(newDesc, "RANDOM_SUBSAMPLED");
- if (!errorStr.isEmpty())
- {
- delete result;
- return cmd.error(errorStr);
- }
- }
- //replace current cloud by this one
- delete desc.pc;
- desc.pc = result;
- desc.basename += "_RANDOM_SUBSAMPLED";
- }
- else
- {
- return cmd.error(QObject::tr("Not enough memory!"));
- }
- }
- }
- else if (method == "SPATIAL")
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: spatial step after \"-%1 SPATIAL\"").arg(COMMAND_SUBSAMPLE));
- }
- bool ok;
- double step = cmd.arguments().takeFirst().toDouble(&ok);
- if (!ok || step <= 0)
- {
- return cmd.error(QObject::tr("Invalid step value for spatial subsampling!"));
- }
- cmd.print(QObject::tr("\tSpatial step: %1").arg(step));
- double sfMinSpacing = 0;
- double sfMaxSpacing = 0;
- bool useActiveSF = false;
- if (!cmd.arguments().empty())
- {
- if (cmd.arguments().front().toUpper() == OPTION_USE_ACTIVE_SF)
- {
- //enable USE_ACTIVE_SF
- useActiveSF = true;
- cmd.arguments().pop_front();
- if (cmd.arguments().size() >= 2)
- {
- bool validMin = false;
- sfMinSpacing = cmd.arguments().takeFirst().toDouble(&validMin);
- bool validMax = false;
- sfMaxSpacing = cmd.arguments().takeFirst().toDouble(&validMax);
- if (!validMin || !validMax || sfMinSpacing < 0 || sfMaxSpacing < 0)
- {
- return cmd.error(QObject::tr("Invalid parameters: Two positive decimal number required after '%1'").arg(OPTION_USE_ACTIVE_SF));
- }
- }
- else
- {
- return cmd.error(QObject::tr("Missing parameters: Two positive decimal number required after '%1'").arg(OPTION_USE_ACTIVE_SF));
- }
- }
- }
- for (CLCloudDesc& desc : cmd.clouds())
- {
- cmd.print(QObject::tr("\tProcessing cloud %1").arg(!desc.pc->getName().isEmpty() ? desc.pc->getName() : "no name"));
- CCCoreLib::CloudSamplingTools::SFModulationParams modParams(false);
- //handle Use Active SF on each cloud
- if (useActiveSF)
- {
- //look for the min and max sf values
- ccScalarField* sf = desc.pc->getCurrentDisplayedScalarField();
- if (!sf)
- {
- //warn the user, not use active SF and keep going
- cmd.warning(QObject::tr("\tCan't use 'Use active SF': no active scalar field. Set one with '-%1'").arg(COMMAND_SET_ACTIVE_SF));
- }
- else
- {
- //found active scalar field
- ScalarType sfMin = CCCoreLib::NAN_VALUE;
- ScalarType sfMax = CCCoreLib::NAN_VALUE;
- if (sf->countValidValues() > 0)
- {
- if (!ccScalarField::ValidValue(sfMin) || sfMin > sf->getMin())
- sfMin = sf->getMin();
- if (!ccScalarField::ValidValue(sfMax) || sfMax < sf->getMax())
- sfMax = sf->getMax();
- if (!ccScalarField::ValidValue(sfMin) || !ccScalarField::ValidValue(sfMax))
- {
- //warn the user, don't use 'Use Active SF' and keep going
- cmd.warning(QObject::tr("\tCan't use 'Use active SF': scalar field '%1' has invalid min/max values.").arg(QString::fromStdString(sf->getName())));
- }
- else
- {
- //everything validated use acitve SF for modulation
- //implementation of modParams.a/b values come from MainWindow::doActionSubsample()
- modParams.enabled = true;
- double deltaSF = static_cast<double>(sfMax) - static_cast<double>(sfMin);
- if (CCCoreLib::GreaterThanEpsilon(deltaSF))
- {
- modParams.a = (sfMaxSpacing - sfMinSpacing) / deltaSF;
- modParams.b = sfMinSpacing - modParams.a * sfMin;
- }
- else
- {
- modParams.a = 0.0;
- modParams.b = sfMin;
- }
- cmd.print(QObject::tr("\tUse active SF: enabled\n\t\tSpacing at SF min (%1): %2\n\t\tSpacing at SF max (%3): %4")
- .arg(sfMin)
- .arg(sfMinSpacing)
- .arg(sfMax)
- .arg(sfMaxSpacing));
- }
- }
- else
- {
- //warn the user, not use Use Active SF and keep going
- cmd.warning(QObject::tr("\tCan't use 'Use active SF': scalar field '%2' does not have any valid value.").arg(COMMAND_SET_ACTIVE_SF).arg(QString::fromStdString(sf->getName())));
- }
- }
- }
- if (useActiveSF && !modParams.enabled)
- {
- cmd.print(QObject::tr("\t'Use active SF' disabled. Falling back to constant spacing."));
- }
- CCCoreLib::ReferenceCloud* refCloud = CCCoreLib::CloudSamplingTools::resampleCloudSpatially(desc.pc, static_cast<PointCoordinateType>(step), modParams, nullptr, cmd.progressDialog());
- if (!refCloud)
- {
- return cmd.error("Subsampling process failed!");
- }
- cmd.print(QObject::tr("\tResult: %1 points").arg(refCloud->size()));
-
- //save output
- ccPointCloud* result = desc.pc->partialClone(refCloud);
- delete refCloud;
- refCloud = nullptr;
-
- if (result)
- {
- result->setName(desc.pc->getName() + QObject::tr(".subsampled"));
- if (cmd.autoSaveMode())
- {
- CLCloudDesc newDesc(result, desc.basename, desc.path, desc.indexInFile);
- QString errorStr = cmd.exportEntity(newDesc, "SPATIAL_SUBSAMPLED");
- if (!errorStr.isEmpty())
- {
- delete result;
- return cmd.error(errorStr);
- }
- }
- //replace current cloud by this one
- delete desc.pc;
- desc.pc = result;
- desc.basename += "_SPATIAL_SUBSAMPLED";
- }
- else
- {
- return cmd.error(QObject::tr("Not enough memory!"));
- }
- }
- }
- else if (method == "OCTREE")
- {
- int octreeLevel = 1;
- double cellSize = 0.0;
- bool byCellSize = false;
- bool byMaxNumberOfPoints = false;
- unsigned maxNumberOfPoints = 0;
- bool isPercent = false;
- double percent = 0.0;
- const int maxOctreeLevel = CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL;
- if (!cmd.arguments().empty())
- {
- //params for automatic OCTREE level calculation based on cell size
- if (cmd.arguments().front() == "CELL_SIZE")
- {
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: octree cell size after \"-%1 OCTREE CELL_SIZE \"").arg(COMMAND_SUBSAMPLE));
- }
- bool ok = false;
- cellSize = cmd.arguments().takeFirst().toDouble(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid parameter: octree cell size after \"-%1 OCTREE CELL_SIZE \"").arg(COMMAND_SUBSAMPLE));
- }
- byCellSize = true;
- cmd.print(QObject::tr("\tOctree cell size: %1").arg(cellSize));
- }
- //params for automatic OCTREE level calculation based on number of points
- else if (cmd.arguments().front() == OPTION_NUMBER_OF_POINTS)
- {
- //local option verified
- byMaxNumberOfPoints = true;
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number of points or option \"%3\" after \"-%1 OCTREE %2 \"").arg(COMMAND_SUBSAMPLE).arg(OPTION_NUMBER_OF_POINTS).arg(OPTION_PERCENT));
- }
- //handle percent argument
- if (cmd.arguments().front() == OPTION_PERCENT)
- {
- //local option verified
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number after \"-%1 OCTREE %2 %3\"").arg(COMMAND_SUBSAMPLE).arg(OPTION_NUMBER_OF_POINTS).arg(OPTION_PERCENT));
- }
- bool ok = false;
- percent = cmd.arguments().takeFirst().toDouble(&ok);
- if (!ok || percent < 0 || percent > 100)
- {
- return cmd.error(QObject::tr("Invalid parameter: number after \"-%1 OCTREE %2 %3\" must be decimal between 0 and 100").arg(COMMAND_SUBSAMPLE).arg(OPTION_NUMBER_OF_POINTS).arg(OPTION_PERCENT));
- }
- isPercent = true;
- }
- else
- {
- bool ok = false;
- maxNumberOfPoints = cmd.arguments().takeFirst().toUInt(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid parameter: number of points or option \"%3\" after \"-%1 OCTREE %2 \"").arg(COMMAND_SUBSAMPLE).arg(OPTION_NUMBER_OF_POINTS).arg(OPTION_PERCENT));
- }
- cmd.print(QObject::tr("\tOctree target number of points: %1").arg(maxNumberOfPoints));
- }
- }
- //params for original version octree calculation based on given level
- else
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: octree level after \"-%1 OCTREE\"").arg(COMMAND_SUBSAMPLE));
- }
- bool ok = false;
- octreeLevel = cmd.arguments().takeFirst().toInt(&ok);
- if (!ok || octreeLevel < 1 || octreeLevel > maxOctreeLevel)
- {
- return cmd.error(QObject::tr("Invalid octree level!"));
- }
- cmd.print(QObject::tr("\tOctree level: %1").arg(octreeLevel));
- }
- }
-
- QScopedPointer<ccProgressDialog> progressDialog(nullptr);
- if (!cmd.silentMode())
- {
- progressDialog.reset(new ccProgressDialog(false, cmd.widgetParent()));
- progressDialog->setAutoClose(false);
- }
-
- for (CLCloudDesc& desc : cmd.clouds())
- {
- //calculate octree before subsampling, it is passed to subsampling, so it won't be recalculated there.
- CCCoreLib::DgmOctree* octree = desc.pc->getOctree().data();
- if (!octree)
- {
- octree = desc.pc->computeOctree(nullptr, false).data();
- }
- if (!octree)
- {
- return cmd.error("Octree calculation failed, not enough memory?");
- }
- CCCoreLib::ReferenceCloud* refCloud = nullptr;
- ccPointCloud* result = nullptr;
- unsigned sizeOfInputCloud = desc.pc->size();
- cmd.print(QObject::tr("\tProcessing cloud %1").arg(!desc.pc->getName().isEmpty() ? desc.pc->getName() : "no name"));
- if (byCellSize)
- {
- //calculate OCTREE level for each cloud based on required octree cell size
- octreeLevel = static_cast<int>(ceil(log(desc.pc->getOwnBB().getMaxBoxDim() / cellSize) / log(2.0)));
- }
- if (byMaxNumberOfPoints)
- {
- if (isPercent)
- {
- maxNumberOfPoints = static_cast<unsigned>(ceil(sizeOfInputCloud * percent / 100));
- cmd.print(QObject::tr("\tOutput point target: %1 * %2% = %3").arg(sizeOfInputCloud).arg(percent).arg(maxNumberOfPoints));
- }
- //calculate OCTREE level for each cloud based on required number of points
- octreeLevel = maxOctreeLevel;
- unsigned numberOfPoints = sizeOfInputCloud;
- //go through max->min until previous point count and current point count is different then break
- for (int currentOctreeLevel = maxOctreeLevel; currentOctreeLevel > 0; --currentOctreeLevel)
- {
- unsigned currentNumberOfPoints = octree->getCellNumber(currentOctreeLevel);
- if (currentNumberOfPoints != numberOfPoints && numberOfPoints < maxNumberOfPoints)
- {
- break;
- }
- octreeLevel = currentOctreeLevel;
- numberOfPoints = currentNumberOfPoints;
- }
- }
- //only process further if CELL_SIZE or octree level was given, or the numberOfPoints smaller than the input cloud
- if (!byMaxNumberOfPoints || (byMaxNumberOfPoints && sizeOfInputCloud > maxNumberOfPoints))
- {
- //overwrite and print out finalized OCTREE level.
- if (byCellSize || byMaxNumberOfPoints)
- {
- //clamp octree level
- octreeLevel = std::max(std::min(octreeLevel, maxOctreeLevel), 1);
- cmd.print(QObject::tr("\tCalculated octree level: %1").arg(octreeLevel));
- }
- refCloud = CCCoreLib::CloudSamplingTools::subsampleCloudWithOctreeAtLevel( desc.pc,
- static_cast<unsigned char>(octreeLevel),
- CCCoreLib::CloudSamplingTools::NEAREST_POINT_TO_CELL_CENTER,
- progressDialog.data(),
- octree);
- if (!refCloud)
- {
- return cmd.error(QObject::tr("Subsampling process failed!"));
- }
- cmd.print(QObject::tr("\tResult: %1 points").arg(refCloud->size()));
- //save output
- result = desc.pc->partialClone(refCloud);
- delete refCloud;
- refCloud = nullptr;
- }
- else
- {
- cmd.print("\tNot subsampled, point count is smaller than max number of points");
- //no subsampling happened so result="input cloud"
- result = desc.pc;
- //set octreeLevel to indicate it was not subsampled at all
- octreeLevel = -1;
- }
- if (result)
- {
- result->setName(desc.pc->getName() + QObject::tr(".subsampled"));
- QString suffix = QObject::tr("OCTREE_LEVEL_%1_SUBSAMPLED").arg(octreeLevel);
- if (cmd.autoSaveMode())
- {
- CLCloudDesc newDesc(result, desc.basename, desc.path, desc.indexInFile);
- QString errorStr = cmd.exportEntity(newDesc, suffix);
- if (!errorStr.isEmpty())
- {
- delete result;
- return cmd.error(errorStr);
- }
- }
- if (desc.pc != result)
- {
- //replace current cloud by subsampled one if it was changed
- delete desc.pc;
- desc.pc = result;
- desc.basename += '_' + suffix;
- }
- }
- else
- {
- return cmd.error(QObject::tr("Not enough memory!"));
- }
- }
-
- if (progressDialog)
- {
- progressDialog->close();
- QCoreApplication::processEvents();
- }
- }
- else
- {
- return cmd.error(QObject::tr("Unknown method!"));
- }
-
- return true;
- }
- CommandExtractCCs::CommandExtractCCs()
- : ccCommandLineInterface::Command(QObject::tr("ExtractCCs"), COMMAND_EXTRACT_CC)
- {}
- bool CommandExtractCCs::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud loaded (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_EXTRACT_CC));
- }
-
- //octree level
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: octree level after \"-%1\"").arg(COMMAND_EXTRACT_CC));
- }
- bool ok;
- unsigned char octreeLevel = std::min<unsigned char>(cmd.arguments().takeFirst().toUShort(&ok), CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid octree level!"));
- }
- cmd.print(QObject::tr("\tOctree level: %1").arg(octreeLevel));
-
- //min number of points
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: minimum number of points per component after \"-%1 [octree level]\"").arg(COMMAND_EXTRACT_CC));
- }
- unsigned minPointCount = cmd.arguments().takeFirst().toUInt(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid min. number of points!"));
- }
- cmd.print(QObject::tr("\tMin number of points per component: %1").arg(minPointCount));
-
- try
- {
- QScopedPointer<ccProgressDialog> progressDialog(nullptr);
- if (!cmd.silentMode())
- {
- progressDialog.reset(new ccProgressDialog(false, cmd.widgetParent()));
- progressDialog->setAutoClose(false);
- }
-
- std::vector<CLCloudDesc> inputClouds = cmd.clouds();
- cmd.clouds().clear();
- for (CLCloudDesc& desc : inputClouds)
- {
- cmd.print(QObject::tr("\tProcessing cloud %1").arg(!desc.pc->getName().isEmpty() ? desc.pc->getName() : "no name"));
-
- //we create/activate CCs label scalar field
- int sfIdx = desc.pc->getScalarFieldIndexByName(CC_CONNECTED_COMPONENTS_DEFAULT_LABEL_NAME);
- if (sfIdx < 0)
- {
- sfIdx = desc.pc->addScalarField(CC_CONNECTED_COMPONENTS_DEFAULT_LABEL_NAME);
- }
- if (sfIdx < 0)
- {
- cmd.error(QObject::tr("Couldn't allocate a new scalar field for computing CC labels! Try to free some memory ..."));
- continue;
- }
- desc.pc->setCurrentScalarField(sfIdx);
-
- //try to label all CCs
- int componentCount = CCCoreLib::AutoSegmentationTools::labelConnectedComponents(desc.pc,
- static_cast<unsigned char>(octreeLevel),
- false,
- progressDialog.data());
-
- if (componentCount == 0)
- {
- cmd.error(QObject::tr("No component found!"));
- continue;
- }
-
- desc.pc->getCurrentInScalarField()->computeMinAndMax();
- CCCoreLib::ReferenceCloudContainer components;
- bool success = CCCoreLib::AutoSegmentationTools::extractConnectedComponents(desc.pc, components);
- desc.pc->deleteScalarField(sfIdx);
- sfIdx = -1;
-
- if (!success)
- {
- cmd.warning(QObject::tr("An error occurred (failed to finish the extraction)"));
- continue;
- }
-
- //we create "real" point clouds for all input components
- int realIndex = 0;
- for (size_t j = 0; j < components.size(); ++j)
- {
- CCCoreLib::ReferenceCloud* compIndexes = components[j];
-
- //if it has enough points
- if (compIndexes->size() >= minPointCount)
- {
- //we create a new entity
- ccPointCloud* compCloud = desc.pc->partialClone(compIndexes);
- if (compCloud)
- {
- //'shift on load' information
- compCloud->copyGlobalShiftAndScale(*desc.pc);
- compCloud->setName(QString(desc.pc->getName() + "_CC#%1").arg(j + 1));
- QString filenameSuffix = QObject::tr("_COMPONENT_%1").arg(++realIndex);
- if (desc.indexInFile >= 0)
- {
- // add the cloud name and its index in the file to avoid overwriting files if mutlitple clouds came from the same file
- filenameSuffix.prepend(QObject::tr("_CLOUD_%1(%2)").arg(desc.pc->getName()).arg(desc.indexInFile));
- }
- CLCloudDesc newDesc(compCloud, desc.basename + filenameSuffix, desc.path);
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(newDesc, QString(), nullptr, ccCommandLineInterface::ExportOption::ForceNoTimestamp);
- if (!errorStr.isEmpty())
- {
- cmd.error(errorStr);
- }
- }
- //add newDesc to the current pool
- cmd.clouds().push_back(newDesc);
- }
- else
- {
- cmd.warning(QObject::tr("Failed to create component #%1! (not enough memory)").arg(j + 1));
- }
- }
-
- delete compIndexes;
- compIndexes = nullptr;
- }
-
- components.clear();
-
- if (cmd.clouds().empty())
- {
- cmd.error(QObject::tr("No component was created! Check the minimum size..."));
- }
- else
- {
- cmd.print(QObject::tr("%1 component(s) were created").arg(cmd.clouds().size()));
- }
- }
-
- if (progressDialog)
- {
- progressDialog->close();
- QCoreApplication::processEvents();
- }
- }
- catch (const std::bad_alloc&)
- {
- cmd.error(QObject::tr("Not enough memory"));
- return false;
- }
-
- return true;
- }
- CommandCurvature::CommandCurvature()
- : ccCommandLineInterface::Command(QObject::tr("Curvature"), COMMAND_CURVATURE)
- {}
- bool CommandCurvature::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: curvature type after \"-%1\"").arg(COMMAND_CURVATURE));
- }
-
- QString curvTypeStr = cmd.arguments().takeFirst().toUpper();
- CCCoreLib::Neighbourhood::CurvatureType curvType = CCCoreLib::Neighbourhood::MEAN_CURV;
- if (curvTypeStr == "MEAN")
- {
- //curvType = CCCoreLib::Neighbourhood::MEAN_CURV;
- }
- else if (curvTypeStr == "GAUSS")
- {
- curvType = CCCoreLib::Neighbourhood::GAUSSIAN_CURV;
- }
- else if (curvTypeStr == "NORMAL_CHANGE")
- {
- curvType = CCCoreLib::Neighbourhood::NORMAL_CHANGE_RATE;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid curvature type after \"-%1\". Got '%2' instead of MEAN or GAUSS.").arg(COMMAND_CURVATURE, curvTypeStr));
- }
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: kernel size after curvature type"));
- }
-
- bool paramOk = false;
- QString kernelStr = cmd.arguments().takeFirst();
- PointCoordinateType kernelSize = static_cast<PointCoordinateType>(kernelStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: kernel size (after curvature type). Got '%1' instead.").arg(kernelStr));
- }
- cmd.print(QObject::tr("\tKernel size: %1").arg(kernelSize));
-
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud on which to compute curvature! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_CURVATURE));
- }
-
- //Call MainWindow generic method
- ccHObject::Container entities;
- entities.resize(cmd.clouds().size());
- for (size_t i = 0; i < cmd.clouds().size(); ++i)
- {
- entities[i] = cmd.clouds()[i].pc;
- }
-
- if (ccLibAlgorithms::ComputeGeomCharacteristic(CCCoreLib::GeometricalAnalysisTools::Curvature, curvType, kernelSize, entities, nullptr, cmd.widgetParent()))
- {
- //save output
- if (cmd.autoSaveMode() && !cmd.saveClouds(QObject::tr("%1_CURVATURE_KERNEL_%2").arg(curvTypeStr).arg(kernelSize)))
- {
- return false;
- }
- }
- return true;
- }
- static bool ReadDensityType(ccCommandLineInterface& cmd, CCCoreLib::GeometricalAnalysisTools::Density& density)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: density type after \"-%1\" (KNN/SURFACE/VOLUME)").arg(COMMAND_DENSITY_TYPE));
- }
-
- //read option confirmed, we can move on
- QString typeArg = cmd.arguments().takeFirst().toUpper();
- if (typeArg == "KNN")
- {
- density = CCCoreLib::GeometricalAnalysisTools::DENSITY_KNN;
- }
- else if (typeArg == "SURFACE")
- {
- density = CCCoreLib::GeometricalAnalysisTools::DENSITY_2D;
- }
- else if (typeArg == "VOLUME")
- {
- density = CCCoreLib::GeometricalAnalysisTools::DENSITY_3D;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: density type is expected after \"-%1\" (KNN/SURFACE/VOLUME)").arg(COMMAND_DENSITY_TYPE));
- }
- return true;
- }
- CommandApproxDensity::CommandApproxDensity()
- : ccCommandLineInterface::Command(QObject::tr("Approx Density"), COMMAND_APPROX_DENSITY)
- {}
- bool CommandApproxDensity::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud on which to compute approx. density! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_APPROX_DENSITY));
- }
-
- //Call MainWindow generic method
- ccHObject::Container entities;
- entities.resize(cmd.clouds().size());
- for (size_t i = 0; i < cmd.clouds().size(); ++i)
- {
- entities[i] = cmd.clouds()[i].pc;
- }
-
- //optional parameter: density type
- CCCoreLib::GeometricalAnalysisTools::Density densityType = CCCoreLib::GeometricalAnalysisTools::DENSITY_3D;
- if (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_DENSITY_TYPE))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: density type after \"-%1\" (KNN/SURFACE/VOLUME)").arg(COMMAND_DENSITY_TYPE));
- }
- //read option confirmed, we can move on
- if (!ReadDensityType(cmd, densityType))
- {
- return false;
- }
- }
- }
-
- if (ccLibAlgorithms::ComputeGeomCharacteristic(CCCoreLib::GeometricalAnalysisTools::ApproxLocalDensity, densityType, 0, entities, nullptr, cmd.widgetParent()))
- {
- //save output
- if (cmd.autoSaveMode() && !cmd.saveClouds("APPROX_DENSITY"))
- {
- return false;
- }
- }
-
- return true;
- }
- CommandDensity::CommandDensity()
- : ccCommandLineInterface::Command(QObject::tr("Density"), COMMAND_DENSITY)
- {}
- bool CommandDensity::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: sphere radius after \"-%1\"").arg(COMMAND_DENSITY));
- }
-
- bool paramOk = false;
- QString kernelStr = cmd.arguments().takeFirst();
- PointCoordinateType kernelSize = static_cast<PointCoordinateType>(kernelStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: sphere radius (after \"-%1\"). Got '%2' instead.").arg(COMMAND_DENSITY, kernelStr));
- }
- cmd.print(QObject::tr("\tSphere radius: %1").arg(kernelSize));
-
- //optional parameter: density type
- CCCoreLib::GeometricalAnalysisTools::Density densityType = CCCoreLib::GeometricalAnalysisTools::DENSITY_3D;
- if (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_DENSITY_TYPE))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: density type after \"-%1\" (KNN/SURFACE/VOLUME)").arg(COMMAND_DENSITY_TYPE));
- }
- //read option confirmed, we can move on
- if (!ReadDensityType(cmd, densityType))
- {
- return false;
- }
- }
- }
-
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud on which to compute density! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_DENSITY));
- }
-
- //Call MainWindow generic method
- ccHObject::Container entities;
- entities.resize(cmd.clouds().size());
- for (size_t i = 0; i < cmd.clouds().size(); ++i)
- {
- entities[i] = cmd.clouds()[i].pc;
- }
-
- if (ccLibAlgorithms::ComputeGeomCharacteristic(CCCoreLib::GeometricalAnalysisTools::LocalDensity, densityType, kernelSize, entities, nullptr, cmd.widgetParent()))
- {
- //save output
- if (cmd.autoSaveMode() && !cmd.saveClouds("DENSITY"))
- {
- return false;
- }
- }
-
- return true;
- }
- CommandSFGradient::CommandSFGradient()
- : ccCommandLineInterface::Command(QObject::tr("SF gradient"), COMMAND_SF_GRADIENT)
- {}
- bool CommandSFGradient::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: boolean (whether SF is euclidean or not) after \"-%1\"").arg(COMMAND_SF_GRADIENT));
- }
-
- QString euclideanStr = cmd.arguments().takeFirst().toUpper();
- bool euclidean = false;
- if (euclideanStr == "TRUE")
- {
- euclidean = true;
- }
- else if (euclideanStr != "FALSE")
- {
- return cmd.error(QObject::tr("Invalid boolean value after \"-%1\". Got '%2' instead of TRUE or FALSE.").arg(COMMAND_SF_GRADIENT, euclideanStr));
- }
-
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud on which to compute SF gradient! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_SF_GRADIENT));
- }
-
- //Call MainWindow generic method
- void* additionalParameters[1] = { &euclidean };
- ccHObject::Container entities;
- entities.reserve(cmd.clouds().size());
- for (CLCloudDesc& desc : cmd.clouds())
- {
- unsigned sfCount = desc.pc->getNumberOfScalarFields();
- if (sfCount == 0)
- {
- cmd.warning(QObject::tr("cmd.warning: cloud '%1' has no scalar field (it will be ignored)").arg(desc.pc->getName()));
- }
- else
- {
- if (sfCount > 1)
- {
- cmd.warning(QObject::tr("cmd.warning: cloud '%1' has several scalar fields (the active one will be used by default, or the first one if none is active)").arg(desc.pc->getName()));
- }
-
- int activeSFIndex = desc.pc->getCurrentOutScalarFieldIndex();
- if (activeSFIndex < 0)
- {
- activeSFIndex = 0;
- }
-
- desc.pc->setCurrentDisplayedScalarField(activeSFIndex);
-
- entities.push_back(desc.pc);
- }
- }
-
- if (ccLibAlgorithms::ApplyCCLibAlgorithm(ccLibAlgorithms::CCLIB_ALGO_SF_GRADIENT, entities, cmd.widgetParent(), additionalParameters))
- {
- //save output
- if (cmd.autoSaveMode() && !cmd.saveClouds(euclidean ? "EUCLIDEAN_SF_GRAD" : "SF_GRAD"))
- {
- return false;
- }
- }
-
- return true;
- }
- CommandRoughness::CommandRoughness()
- : ccCommandLineInterface::Command(QObject::tr("Roughness"), COMMAND_ROUGHNESS)
- {}
- bool CommandRoughness::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: kernel size after \"-%1\"").arg(COMMAND_ROUGHNESS));
- }
-
- bool paramOk = false;
- QString kernelStr = cmd.arguments().takeFirst();
- PointCoordinateType kernelSize = static_cast<PointCoordinateType>(kernelStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: kernel size (after \"-%1\"). Got '%2' instead.").arg(COMMAND_ROUGHNESS, kernelStr));
- }
- cmd.print(QObject::tr("\tKernel size: %1").arg(kernelSize));
- // optional argument
- CCVector3 roughnessUpDir;
- CCVector3* _roughnessUpDir = nullptr;
- if (cmd.arguments().size() >= 4)
- {
- QString nextArg = cmd.arguments().first();
- if (nextArg.startsWith('-') && nextArg.mid(1).toUpper() == COMMAND_ROUGHNESS_UP_DIR)
- {
- // option confirmed
- cmd.arguments().takeFirst();
- QString xStr = cmd.arguments().takeFirst();
- QString yStr = cmd.arguments().takeFirst();
- QString zStr = cmd.arguments().takeFirst();
- bool okX = false, okY = false, okZ = false;
- roughnessUpDir.x = static_cast<PointCoordinateType>(xStr.toDouble(&okX));
- roughnessUpDir.y = static_cast<PointCoordinateType>(yStr.toDouble(&okY));
- roughnessUpDir.z = static_cast<PointCoordinateType>(zStr.toDouble(&okZ));
- if (!okX || !okY || !okZ)
- {
- return cmd.error(QObject::tr("Invalid 'up direction' vector after option -%1 (3 coordinates expected)").arg(COMMAND_ROUGHNESS_UP_DIR));
- }
- _roughnessUpDir = &roughnessUpDir;
- }
- }
-
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud on which to compute roughness! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_ROUGHNESS));
- }
-
- //Call MainWindow generic method
- ccHObject::Container entities;
- entities.resize(cmd.clouds().size());
- for (size_t i = 0; i < cmd.clouds().size(); ++i)
- {
- entities[i] = cmd.clouds()[i].pc;
- }
-
- if (ccLibAlgorithms::ComputeGeomCharacteristic(CCCoreLib::GeometricalAnalysisTools::Roughness, 0, kernelSize, entities, _roughnessUpDir, cmd.widgetParent()))
- {
- //save output
- if (cmd.autoSaveMode() && !cmd.saveClouds(QObject::tr("ROUGHNESS_KERNEL_%2").arg(kernelSize)))
- {
- return false;
- }
- }
-
- return true;
- }
- CommandApplyTransformation::CommandApplyTransformation()
- : ccCommandLineInterface::Command(QObject::tr("Apply Transformation"), COMMAND_APPLY_TRANSFORMATION)
- {}
- bool CommandApplyTransformation::process(ccCommandLineInterface& cmd)
- {
- //optional parameters
- bool inverse = false;
- bool applyToGlobal = false;
- bool forceApplyToGlobal = false;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_APPLY_TRANS_INVERSE))
- {
- //local option confirmed, we can move on
- inverse = true;
- cmd.arguments().pop_front();
- cmd.print("Transformation matrix will be inversed");
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_APPLY_TRANS_TO_GLOBAL))
- {
- //local option confirmed, we can move on
- applyToGlobal = true;
- cmd.arguments().pop_front();
- cmd.print("Transformation will be applied to the global coordinates (Global Shift may be automatically adjusted to preserve accuracy)");
- //look for the 'FORCE' sub-option
- if (!cmd.arguments().empty() && cmd.arguments().front().toUpper() == OPTION_FORCE)
- {
- //local option confirmed, we can move on
- forceApplyToGlobal = true;
- cmd.arguments().pop_front();
- cmd.print("Transformation will be applied to the global coordinates even if the entity already has large coordinates");
- }
- }
- else
- {
- break;
- }
- }
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: transformation file after \"-%1\"").arg(COMMAND_APPLY_TRANSFORMATION));
- }
-
- QString filename = cmd.arguments().takeFirst();
- ccGLMatrixd mat;
- if (!mat.fromAsciiFile(filename))
- {
- return cmd.error(QObject::tr("Failed to read transformation matrix file '%1'!").arg(filename));
- }
- if (inverse)
- {
- cmd.print(QObject::tr("Transformation before inversion:\n") + mat.toString());
- mat = mat.inverse();
- }
-
- cmd.print(QObject::tr("Transformation:\n") + mat.toString());
-
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No entity on which to apply the transformation! (be sure to open one with \"-%1 [filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_APPLY_TRANSFORMATION));
- }
- //create an entity vector
- size_t nrOfClouds = cmd.clouds().size();
- size_t nrOfMeshes = cmd.meshes().size();
- size_t nrOfEntities = nrOfClouds + nrOfMeshes;
- std::vector<std::pair<ccShiftedObject*, CLEntityDesc*>> entities;
- entities.reserve(nrOfEntities);
- //add clouds to the vector
- for (CLCloudDesc& desc : cmd.clouds())
- {
- entities.push_back({ desc.pc, &desc });
- }
- //add meshes to the vector
- for (CLMeshDesc& desc : cmd.meshes())
- {
- bool isLocked = false;
- ccShiftedObject* shifted = ccHObjectCaster::ToShifted(desc.mesh, &isLocked);
- if (shifted && !isLocked)
- {
- entities.push_back({ shifted, &desc });
- }
- }
- //if the transformation is partly converted to global shift/scale
- bool autoApplyPreviousGlobalShiftAndScale = false;
- double previousScale = 1.0;
- CCVector3d previousShift(0, 0, 0);
- //process both clouds and meshes
- for (size_t i = 0; i < nrOfEntities; i++)
- {
- CLEntityDesc& desc = *entities[i].second;
- ccShiftedObject* shiftedEntity = entities[i].first;
- ccGLMatrixd transMat(mat);
- if (applyToGlobal)
- {
- // the user wants to apply the transformation to the global coordinates
- CCVector3d globalShift = shiftedEntity->getGlobalShift();
- double globalScale = shiftedEntity->getGlobalScale();
- // we compute the impact to the local coordinate system without changing the
- // actual Global Shift & Scale parameters (for now)
- CCVector3d localTranslation = globalScale * (globalShift - transMat * globalShift + 2 * transMat.getTranslationAsVec3D());
- // we switch to a local transformation matrix
- transMat.setTranslation(localTranslation);
- //test if the translated cloud coordinates were already "too large"
- ccBBox localBBox = shiftedEntity->getOwnBB();
- CCVector3d Pl = localBBox.minCorner();
- double Dl = localBBox.getDiagNormd();
- if (forceApplyToGlobal || (!ccGlobalShiftManager::NeedShift(Pl) && !ccGlobalShiftManager::NeedRescale(Dl)))
- {
- //test if the translated (local) cloud coordinates are too large
- ccBBox transformedLocalBox = localBBox * transMat;
- CCVector3d transformedPl = transformedLocalBox.minCorner();
- double transformedDl = transformedLocalBox.getDiagNormd();
- bool needShift = ccGlobalShiftManager::NeedShift(transformedPl) || ccGlobalShiftManager::NeedRescale(transformedDl);
- if (needShift)
- {
- //existing shift information
- CCVector3d globalShift = shiftedEntity->getGlobalShift();
- double globalScale = shiftedEntity->getGlobalScale();
- //we compute the global coordinates and scale of the reference point (= the min corner of the bounding-box)
- CCVector3d Pg = shiftedEntity->toGlobal3d(transformedPl);
- double Dg = transformedDl / globalScale;
- //let's try to find better Global Shift and Scale values
- CCVector3d newShift(0.0, 0.0, 0.0);
- double newScale = 1.0;
- //should we try to use the previous Global Shift and Scale values?
- if (autoApplyPreviousGlobalShiftAndScale)
- {
- if (!ccGlobalShiftManager::NeedShift(Pg + previousShift) && !ccGlobalShiftManager::NeedRescale(Dg * previousScale))
- {
- newScale = previousScale;
- newShift = previousShift;
- needShift = false;
- }
- }
- if (needShift)
- {
- //don't bother the user as we are running in command line, use the best possible solution
- newShift = ccGlobalShiftManager::BestShift(Pg);
- newScale = ccGlobalShiftManager::BestScale(Dg);
- needShift = false;
- //force this shift for upcoming entities
- autoApplyPreviousGlobalShiftAndScale = true;
- previousShift = newShift;
- previousScale = newScale;
- }
- //get the relative modification to existing global shift/scale info
- double scaleChange = newScale / globalScale;
- CCVector3d shiftChange = newShift - globalShift;
- if (scaleChange != 1.0 || shiftChange.norm2() != 0)
- {
- //apply translation as global shift
- shiftedEntity->setGlobalShift(newShift);
- shiftedEntity->setGlobalScale(newScale);
- cmd.warning(QObject::tr("Entity '%1' global shift/scale information has been updated: shift = (%2,%3,%4) / scale = %5")
- .arg(shiftedEntity->getName())
- .arg(newShift.x)
- .arg(newShift.y)
- .arg(newShift.z)
- .arg(newScale));
- transMat.scaleRotation(scaleChange);
- transMat.setTranslation(transMat.getTranslationAsVec3D() + newScale * shiftChange);
- }
- }
- }
- else
- {
- cmd.warning(QObject::tr("Entity '%1' already has very large local coordinates. Global shift/scale won't be automatically adjusted to preserve accuracy. Consider using the -%2 option to force global shift/scale adjustment.").arg(shiftedEntity->getName()).arg(OPTION_FORCE));
- }
- }
- else
- {
- // check if the transformed coordinates are too large
- ccBBox transformedLocalBox = shiftedEntity->getOwnBB() * transMat;
- CCVector3d transformedPl = transformedLocalBox.minCorner();
- double transformedDl = transformedLocalBox.getDiagNormd();
- bool needShift = ccGlobalShiftManager::NeedShift(transformedPl) || ccGlobalShiftManager::NeedRescale(transformedDl);
- if (needShift)
- {
- cmd.warning(QObject::tr("Entity '%1' will have very large local coordinates after transformation. Consider using the -%1 option to preserve accuracy.").arg(COMMAND_APPLY_TRANS_TO_GLOBAL));
- }
- }
- ccGLMatrix matrixToApply(transMat.data());
- shiftedEntity->applyGLTransformation_recursive(&matrixToApply);
- QString nameSuffix = "_TRANSFORMED";
- if ((&desc)->getCLEntityType() == CL_ENTITY_TYPE::MESH)
- {
- //set the mesh name instead of the vertices cloud inside the mesh
- ccHObject* parent = shiftedEntity->getParent();
- if (parent)
- {
- parent->setName(QObject::tr("%1%2").arg(parent->getName()).arg(nameSuffix));
- }
- }
- else
- {
- shiftedEntity->setName(QObject::tr("%1%2").arg(shiftedEntity->getName()).arg(nameSuffix));
- }
- desc.basename += nameSuffix;
- //save it as well
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- return true;
- }
- CommandDropGlobalShift::CommandDropGlobalShift()
- : ccCommandLineInterface::Command(QObject::tr("Drop global shift"), COMMAND_DROP_GLOBAL_SHIFT)
- {}
- bool CommandDropGlobalShift::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No loaded entity! (be sure to open one with \"-%1 [filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_DROP_GLOBAL_SHIFT));
- }
-
- //process clouds
- for (const CLCloudDesc& desc : cmd.clouds())
- {
- desc.pc->setGlobalShift(0, 0, 0);
- }
-
- for (const CLMeshDesc& desc : cmd.meshes())
- {
- bool isLocked = false;
- ccShiftedObject* shifted = ccHObjectCaster::ToShifted(desc.mesh, &isLocked);
- if (shifted && !isLocked)
- {
- shifted->setGlobalShift(0, 0, 0);
- }
- }
-
- return true;
- }
- CommandSFColorScale::CommandSFColorScale()
- : ccCommandLineInterface::Command(QObject::tr("SF color scale"), COMMAND_SF_COLOR_SCALE)
- {}
- bool CommandSFColorScale::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: color scale file after \"-%1\"").arg(COMMAND_SF_COLOR_SCALE));
- }
-
- QString filename = cmd.arguments().takeFirst();
-
- ccColorScale::Shared scale = ccColorScale::LoadFromXML(filename);
-
- if (!scale)
- {
- return cmd.error(QObject::tr("Failed to read color scale file '%1'!").arg(filename));
- }
-
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No point cloud or mesh on which to set the SF color scale! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_SF_COLOR_SCALE));
- }
-
- // clouds
- if (!cmd.clouds().empty())
- {
- bool hasCandidateClouds = false;
- for (auto& cloud : cmd.clouds())
- {
- ccScalarField* sf = static_cast<ccScalarField*>(cloud.pc->getCurrentOutScalarField());
- if (sf)
- {
- sf->setColorScale(scale);
- hasCandidateClouds = true;
- }
- }
- if (hasCandidateClouds && cmd.autoSaveMode() && !cmd.saveClouds("COLOR_SCALE"))
- {
- return false;
- }
- }
-
- // meshes
- if (!cmd.meshes().empty())
- {
- bool hasCandidateMeshes = false;
- for (auto& mesh : cmd.meshes())
- {
- ccPointCloud* vertices = dynamic_cast<ccPointCloud*>(mesh.mesh->getAssociatedCloud());
- if (vertices)
- {
- ccScalarField* sf = static_cast<ccScalarField*>(vertices->getCurrentOutScalarField());
- if (sf)
- {
- sf->setColorScale(scale);
- hasCandidateMeshes = true;
- }
- }
- }
- if (hasCandidateMeshes && cmd.autoSaveMode() && !cmd.saveMeshes("COLOR_SCALE"))
- {
- return false;
- }
- }
- return true;
- }
- CommandSFConvertToRGB::CommandSFConvertToRGB()
- : ccCommandLineInterface::Command(QObject::tr("SF convert to RGB"), COMMAND_SF_CONVERT_TO_RGB)
- {}
- bool CommandSFConvertToRGB::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: boolean (whether to mix with existing colors or not) after \"-%1\"").arg(COMMAND_SF_CONVERT_TO_RGB));
- }
-
- QString mixWithExistingColorsStr = cmd.arguments().takeFirst().toUpper();
- bool mixWithExistingColors = false;
- if (mixWithExistingColorsStr == "TRUE")
- {
- mixWithExistingColors = true;
- }
- else if (mixWithExistingColorsStr != "FALSE")
- {
- return cmd.error(QObject::tr("Invalid boolean value after \"-%1\". Got '%2' instead of TRUE or FALSE.").arg(COMMAND_SF_CONVERT_TO_RGB, mixWithExistingColorsStr));
- }
-
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud on which to convert SF to RGB! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_SF_CONVERT_TO_RGB));
- }
-
- for (CLCloudDesc& desc : cmd.clouds())
- {
- unsigned sfCount = desc.pc->getNumberOfScalarFields();
- int activeSFIndex = desc.pc->getCurrentOutScalarFieldIndex();
-
- if (sfCount == 0)
- {
- cmd.warning(QObject::tr("cmd.warning: cloud '%1' has no scalar field (it will be ignored)").arg(desc.pc->getName()));
- }
- else if (activeSFIndex < 0)
- {
- cmd.warning(QObject::tr("cmd.warning: cloud '%1' has no active scalar field (it will be ignored)").arg(desc.pc->getName()));
- }
- else
- {
- int displaySFIndex = desc.pc->getCurrentDisplayedScalarFieldIndex();
- desc.pc->setCurrentDisplayedScalarField(activeSFIndex);
-
- if (desc.pc->convertCurrentScalarFieldToColors(mixWithExistingColors))
- {
- desc.pc->showColors(true);
- desc.pc->showSF(false);
- }
- else
- {
- cmd.warning(QObject::tr("cmd.warning: cloud '%1' failed to convert SF to RGB").arg(desc.pc->getName()));
- }
-
- desc.pc->setCurrentDisplayedScalarField(displaySFIndex);
- }
- }
-
- if (cmd.autoSaveMode() && !cmd.saveClouds("SF_CONVERT_TO_RGB"))
- {
- return false;
- }
-
- return true;
- }
- CommandRGBConvertToSF::CommandRGBConvertToSF()
- : ccCommandLineInterface::Command(QObject::tr("RGB convert to SF"), COMMAND_RGB_CONVERT_TO_SF)
- {}
- bool CommandRGBConvertToSF::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud on which to convert RGB to SF! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_RGB_CONVERT_TO_SF));
- }
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (!desc.pc->hasColors())
- {
- cmd.warning(QObject::tr("Cloud %1 has no colors").arg(desc.pc->getName()));
- continue;
- }
- ccHObject::Container container;
- container.push_back(desc.pc);
- if (!ccEntityAction::sfFromColor(container, /*exportR=*/true, /*exportG=*/true, /*exportB=*/true, /*exportAlpha=*/true, /*exportC=*/true)) //beta version, only composite
- {
- return cmd.error(QObject::tr("Failed to convert RGB to scalar fields"));
- }
-
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc, "_RGB_TO_SF");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- return true;
- }
- CommandFilterBySFValue::CommandFilterBySFValue()
- : ccCommandLineInterface::Command(QObject::tr("Filter by SF value"), COMMAND_FILTER_SF_BY_VALUE)
- {}
- //special SF values that can be used instead of explicit ones
- enum USE_SPECIAL_SF_VALUE
- {
- USE_NONE,
- USE_MIN,
- USE_DISP_MIN,
- USE_SAT_MIN,
- USE_N_SIGMA_MIN,
- USE_MAX,
- USE_DISP_MAX,
- USE_SAT_MAX,
- USE_N_SIGMA_MAX
- };
- static std::pair<ScalarType, ScalarType> GetSFRange(const CCCoreLib::ScalarField& sf,
- ScalarType minVal,
- USE_SPECIAL_SF_VALUE useValForMin,
- ScalarType maxVal,
- USE_SPECIAL_SF_VALUE useValForMax)
- {
- ScalarType thisMinVal = minVal;
- {
- switch (useValForMin)
- {
- case USE_MIN:
- thisMinVal = sf.getMin();
- break;
- case USE_DISP_MIN:
- thisMinVal = static_cast<const ccScalarField&>(sf).displayRange().start();
- break;
- case USE_SAT_MIN:
- thisMinVal = static_cast<const ccScalarField&>(sf).saturationRange().start();
- break;
- case USE_N_SIGMA_MIN:
- ScalarType mean;
- ScalarType variance;
- sf.computeMeanAndVariance(mean, &variance);
- thisMinVal = mean - (sqrt(variance) * minVal);
- break;
- default:
- //nothing to do
- break;
- }
- }
- ScalarType thisMaxVal = maxVal;
- {
- switch (useValForMax)
- {
- case USE_MAX:
- thisMaxVal = sf.getMax();
- break;
- case USE_DISP_MAX:
- thisMaxVal = static_cast<const ccScalarField&>(sf).displayRange().stop();
- break;
- case USE_SAT_MAX:
- thisMaxVal = static_cast<const ccScalarField&>(sf).saturationRange().stop();
- break;
- case USE_N_SIGMA_MAX:
- ScalarType mean;
- ScalarType variance;
- sf.computeMeanAndVariance(mean, &variance);
- thisMaxVal = mean + (sqrt(variance) * maxVal);
- break;
- default:
- //nothing to do
- break;
- }
- }
- return { thisMinVal, thisMaxVal };
- }
- static ScalarType GetSFValue(const ccPointCloud& pc, int sfIndex, ScalarType value, USE_SPECIAL_SF_VALUE useVal)
- {
- CCCoreLib::ScalarField* sf = pc.getScalarField(sfIndex);
- //should be handled way before this point this is just safety
- if (sf)
- {
- std::pair<ScalarType, ScalarType> range = GetSFRange(*sf, value, useVal, value, useVal);
- if (useVal <= USE_N_SIGMA_MIN)
- {
- return range.first;
- }
- else
- {
- return range.second;
- }
- }
- return 1.0;
- }
- static USE_SPECIAL_SF_VALUE ToSpecialSFValue(QString valString)
- {
- valString = valString.toUpper();
- if (valString == "MIN")
- {
- return USE_MIN;
- }
- else if (valString == "DISP_MIN")
- {
- return USE_DISP_MIN;
- }
- else if (valString == "SAT_MIN")
- {
- return USE_SAT_MIN;
- }
- else if (valString == "N_SIGMA_MIN")
- {
- return USE_N_SIGMA_MIN;
- }
- else if (valString == "MAX")
- {
- return USE_MAX;
- }
- else if (valString == "DISP_MAX")
- {
- return USE_DISP_MAX;
- }
- else if (valString == "SAT_MAX")
- {
- return USE_SAT_MAX;
- }
- else if (valString == "N_SIGMA_MAX")
- {
- return USE_N_SIGMA_MAX;
- }
- else
- {
- return USE_NONE;
- }
- }
- bool CommandFilterBySFValue::process(ccCommandLineInterface& cmd)
- {
- USE_SPECIAL_SF_VALUE useValForMin = USE_NONE;
- ScalarType minVal = 0;
- QString minValStr;
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: min value after \"-%1\"").arg(COMMAND_FILTER_SF_BY_VALUE));
- }
-
- bool paramOk = false;
- minValStr = cmd.arguments().takeFirst();
- useValForMin = ToSpecialSFValue(minValStr);
- if (useValForMin == USE_N_SIGMA_MIN)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: N value (after \"-%1 N_SIGMA_MIN\").").arg(COMMAND_FILTER_SF_BY_VALUE));
- }
- minValStr = cmd.arguments().takeFirst();
- minVal = static_cast<ScalarType>(minValStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: N value (after \"N_SIGMA_MIN\"). Got '%2' instead.").arg(minValStr));
- }
- }
- else if (useValForMin == USE_N_SIGMA_MAX)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: N value (after \"-%1 N_SIGMA_MAX\").").arg(COMMAND_FILTER_SF_BY_VALUE));
- }
- minValStr = cmd.arguments().takeFirst();
- minVal = static_cast<ScalarType>(minValStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: N value (after \"N_SIGMA_MAX\"). Got '%2' instead.").arg(minValStr));
- }
- }
- else if (useValForMin == USE_NONE)
- {
- minVal = static_cast<ScalarType>(minValStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: min value (after \"-%1\"). Got '%2' instead.").arg(COMMAND_FILTER_SF_BY_VALUE, minValStr));
- }
- }
- }
-
- USE_SPECIAL_SF_VALUE useValForMax = USE_NONE;
- ScalarType maxVal = 0;
- QString maxValStr;
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: max value after \"-%1\" {min}").arg(COMMAND_FILTER_SF_BY_VALUE));
- }
-
- bool paramOk = false;
- maxValStr = cmd.arguments().takeFirst();
- useValForMax = ToSpecialSFValue(maxValStr);
- if (useValForMax == USE_N_SIGMA_MIN)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: N value (after \"-%1 XXX N_SIGMA_MIN\").").arg(COMMAND_FILTER_SF_BY_VALUE));
- }
- maxValStr = cmd.arguments().takeFirst();
- maxVal = static_cast<ScalarType>(maxValStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: N value (after \"N_SIGMA_MIN\"). Got '%2' instead.").arg(maxValStr));
- }
- }
- else if (useValForMax == USE_N_SIGMA_MAX)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: N value (after \"-%1 XXX N_SIGMA_MAX\").").arg(COMMAND_FILTER_SF_BY_VALUE));
- }
- maxValStr = cmd.arguments().takeFirst();
- maxVal = static_cast<ScalarType>(maxValStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: N value (after \"N_SIGMA_MAX\"). Got '%2' instead.").arg(maxValStr));
- }
- }
- else if (useValForMax == USE_NONE)
- {
- maxVal = static_cast<ScalarType>(maxValStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: max value (after min value). Got '%1' instead.").arg(COMMAND_FILTER_SF_BY_VALUE, maxValStr));
- }
- }
- }
-
- cmd.print(QObject::tr("\tInterval: [%1 - %2]").arg(minValStr, maxValStr));
-
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No point cloud nor mesh on which to filter SF! (be sure to open one or generate one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_FILTER_SF_BY_VALUE));
- }
- // for each cloud
- for (CLCloudDesc& desc : cmd.clouds())
- {
- CCCoreLib::ScalarField* sf = desc.pc->getCurrentOutScalarField();
- if (sf)
- {
- std::pair<ScalarType, ScalarType> range = GetSFRange(*sf, minVal, useValForMin, maxVal, useValForMax);
-
- ccPointCloud* fitleredCloud = desc.pc->filterPointsByScalarValue(range.first, range.second);
- if (fitleredCloud)
- {
- cmd.print(QObject::tr("\t\tCloud '%1' --> %2/%3 points remaining").arg(desc.pc->getName()).arg(fitleredCloud->size()).arg(desc.pc->size()));
-
- if (fitleredCloud != desc.pc)
- {
- //replace current cloud by this one
- delete desc.pc;
- desc.pc = fitleredCloud;
- }
- desc.basename += QObject::tr("_FILTERED_[%1_%2]").arg(range.first).arg(range.second);
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
-
- // for each mesh
- for (CLMeshDesc& desc : cmd.meshes())
- {
- ccGenericMesh* mesh = desc.mesh;
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(mesh);
- if (!pc)
- {
- // strange mesh
- continue;
- }
-
- CCCoreLib::ScalarField* sf = pc->getCurrentOutScalarField();
- if (sf)
- {
- std::pair<ScalarType, ScalarType> range = GetSFRange(*sf, minVal, useValForMin, maxVal, useValForMax);
- pc->hidePointsByScalarValue(range.first, range.second);
- ccGenericMesh* filteredMesh = nullptr;
- if (mesh->isA(CC_TYPES::MESH)/*|| ent->isKindOf(CC_TYPES::PRIMITIVE)*/) //TODO
- filteredMesh = ccHObjectCaster::ToMesh(mesh)->createNewMeshFromSelection(false);
- else if (mesh->isA(CC_TYPES::SUB_MESH))
- filteredMesh = ccHObjectCaster::ToSubMesh(mesh)->createNewSubMeshFromSelection(false);
- else
- {
- cmd.warning("Unhandled mesh type for entitiy " + mesh->getName());
- continue;
- }
- if (filteredMesh)
- {
- cmd.print(QObject::tr("\t\tMesh '%1' --> %2/%3 triangles remaining").arg(mesh->getName()).arg(filteredMesh->size()).arg(mesh->size()));
- //replace current mesh by this one
- if (filteredMesh != mesh) //it's technically possible to have the same pointer if all triangles were filtered (with 'createNewMeshFromSelection')
- {
- delete mesh;
- mesh = nullptr;
- desc.mesh = filteredMesh;
- }
- desc.basename += QObject::tr("_FILTERED_[%1_%2]").arg(range.first).arg(range.second);
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
- return true;
- }
- CommandComputeMeshVolume::CommandComputeMeshVolume()
- : ccCommandLineInterface::Command(QObject::tr("Compute mesh volume"), COMMAND_MESH_VOLUME)
- {}
- bool CommandComputeMeshVolume::process(ccCommandLineInterface& cmd)
- {
- if (cmd.meshes().empty())
- {
- cmd.warning(QObject::tr("No mesh loaded! Nothing to do..."));
- return true;
- }
-
- //optional parameters
- QString outputFilename;
- if (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_VOLUME_TO_FILE))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (!cmd.arguments().empty())
- {
- outputFilename = cmd.arguments().front();
- cmd.arguments().pop_front();
- cmd.print(QObject::tr("Volume report file: %1").arg(outputFilename));
- }
- else
- {
- return cmd.error(QObject::tr("Missing argument: filename after '%1'").arg(COMMAND_VOLUME_TO_FILE));
- }
- }
- }
-
- QFile outFile;
- QTextStream outStream(&outFile);
- if (!outputFilename.isEmpty())
- {
- outFile.setFileName(outputFilename);
- if (!outFile.open(QFile::WriteOnly | QFile::Text))
- {
- return cmd.error(QObject::tr("Failed to create/open volume report file"));
- }
- }
-
- //for each mesh
- for (CLMeshDesc& desc : cmd.meshes())
- {
- //we compute the mesh volume
- double V = CCCoreLib::MeshSamplingTools::computeMeshVolume(desc.mesh);
-
- QString titleStr = QObject::tr("Mesh '%1'").arg(desc.basename);
- if (desc.indexInFile >= 0)
- {
- titleStr += QObject::tr(" (#%2)").arg(desc.indexInFile);
- }
- cmd.print(titleStr);
- QString volumeStr = QObject::tr("V = %2").arg(V, 0, 'f', 8);
- cmd.print(volumeStr);
-
- if (outFile.isOpen())
- {
- outStream << titleStr << endl;
- outStream << volumeStr << endl;
- }
- }
-
- return true;
- }
- CommandMergeMeshes::CommandMergeMeshes()
- : ccCommandLineInterface::Command(QObject::tr("Merge meshes"), COMMAND_MERGE_MESHES)
- {}
- bool CommandMergeMeshes::process(ccCommandLineInterface& cmd)
- {
- if (cmd.meshes().size() < 2)
- {
- cmd.warning(QObject::tr("Less than 2 meshes are loaded! Nothing to do..."));
- return true;
- }
-
- CLMeshDesc mergedMeshDesc;
- bool firstValidMesh = true;
-
- //create the destination mesh
- ccPointCloud* vertices = new ccPointCloud("vertices");
- QScopedPointer<ccMesh> mergedMesh(new ccMesh(vertices));
- mergedMesh->setName("Merged mesh");
- mergedMesh->addChild(vertices);
- vertices->setEnabled(false);
-
- //merge meshes
- for (CLMeshDesc& desc : cmd.meshes())
- {
- //get the mesh
- ccMesh* mesh = dynamic_cast<ccMesh*>(desc.mesh);
- if (!mesh)
- {
- ccLog::Error(QObject::tr("Can't merge mesh '%1' (unhandled type)").arg(desc.basename));
- }
-
- if (mergedMesh->merge(mesh, true)) //merge it
- {
- if (firstValidMesh)
- {
- //copy the first valid mesh description
- mergedMeshDesc = desc;
- mergedMeshDesc.mesh = nullptr;
- firstValidMesh = false;
- }
- }
- else
- {
- return cmd.error(QObject::tr("Merge operation failed"));
- }
-
- delete desc.mesh;
- desc.mesh = nullptr;
- }
-
- if (mergedMesh->size() == 0)
- {
- return cmd.error(QObject::tr("Result is empty"));
- }
-
- //clean the 'cmd.meshes()' vector
- cmd.removeMeshes();
- //add the new mesh
- mergedMeshDesc.basename += QObject::tr("_MERGED");
- mergedMeshDesc.mesh = mergedMesh.take();
- cmd.meshes().push_back(mergedMeshDesc);
-
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(mergedMeshDesc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
-
- return true;
- }
- CommandMergeClouds::CommandMergeClouds()
- : ccCommandLineInterface::Command(QObject::tr("Merge clouds"), COMMAND_MERGE_CLOUDS)
- {}
- bool CommandMergeClouds::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().size() < 2)
- {
- cmd.warning(QObject::tr("Less than 2 clouds are loaded! Nothing to do..."));
- return true;
- }
-
- //merge clouds
- if (!cmd.clouds().empty())
- {
- for (size_t i = 1; i < cmd.clouds().size(); ++i)
- {
- unsigned beforePts = cmd.clouds().front().pc->size();
- CLCloudDesc& desc = cmd.clouds()[i];
- unsigned newPts = desc.pc->size();
- *cmd.clouds().front().pc += desc.pc;
-
- //success?
- if (cmd.clouds().front().pc->size() == beforePts + newPts)
- {
- delete desc.pc;
- desc.pc = nullptr;
- }
- else
- {
- return cmd.error(QObject::tr("Fusion failed! (not enough memory?)"));
- }
- }
- }
-
- //clean the 'cmd.clouds()' vector
- cmd.clouds().resize(1);
- //update the first one
- cmd.clouds().front().basename += QObject::tr("_MERGED");
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(cmd.clouds().front());
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- return true;
- }
- CommandSetGlobalShift::CommandSetGlobalShift()
- : ccCommandLineInterface::Command(QObject::tr("Set global shift"), COMMAND_SET_GLOBAL_SHIFT)
- {}
- bool CommandSetGlobalShift::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No loaded entity! (be sure to open one with \"-%1 [filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_SET_GLOBAL_SHIFT));
- }
- //process globalshift options first
- ccCommandLineInterface::GlobalShiftOptions globalShiftOptions;
- cmd.processGlobalShiftCommand(globalShiftOptions);
- //if it is not a valid global shift then an error msg already issued.
- if (globalShiftOptions.mode != ccCommandLineInterface::GlobalShiftOptions::Mode::CUSTOM_GLOBAL_SHIFT)
- {
- return cmd.error(QObject::tr("Global shift must be in the form of three coordinates 'x' 'y' 'z'"));
- }
- CCVector3d newShift = globalShiftOptions.customGlobalShift;
- //look for additional parameters
- bool keepOrigFixed = false;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_SET_GLOBAL_SHIFT_KEEP_ORIG_FIXED))
- {
- //local option confirmed, pop that from front
- cmd.arguments().pop_front();
- keepOrigFixed = true;
- cmd.print(QObject::tr("[%1]").arg(COMMAND_SET_GLOBAL_SHIFT_KEEP_ORIG_FIXED));
- }
- else
- {
- break;
- }
- }
- //create an entity vector
- size_t nrOfClouds = cmd.clouds().size();
- size_t nrOfMeshes = cmd.meshes().size();
- size_t nrOfEntities = nrOfClouds + nrOfMeshes;
- std::vector<std::pair<ccShiftedObject*, CLEntityDesc*>> entities;
- entities.reserve(nrOfEntities);
- //add clouds to the vector
- for (CLCloudDesc& desc : cmd.clouds())
- {
- entities.push_back({ desc.pc, &desc });
- }
- //add meshes to the vector
- for (CLMeshDesc& desc : cmd.meshes())
- {
- bool isLocked = false;
- ccShiftedObject* shifted = ccHObjectCaster::ToShifted(desc.mesh, &isLocked);
- if (shifted && !isLocked)
- {
- entities.push_back({ shifted, &desc });
- }
- }
- //process both clouds and meshes
- for (const auto& pair : entities)
- {
- CLEntityDesc& desc = *pair.second;
- ccShiftedObject* shiftedObject = pair.first;
- CCVector3d originalShift = shiftedObject->getGlobalShift();
- cmd.print(QObject::tr("\t[%4 - %5] Original global shift {%1,%2,%3}")
- .arg(originalShift.x)
- .arg(originalShift.y)
- .arg(originalShift.z)
- .arg(desc.basename)
- .arg(shiftedObject->getName()));
- //translate entity to keep the initial global origin
- if (keepOrigFixed)
- {
- CCVector3d T = newShift - originalShift;
- ccGLMatrix transMat;
- double maxCoordValue = ccGlobalShiftManager::MaxCoordinateAbsValue();
- if (T.x > maxCoordValue || T.y > maxCoordValue || T.z > maxCoordValue)
- {
- cmd.warning(QObject::tr("\t[%5 - %6] Applied transformation is bigger {%1,%2,%3} than the threshold {%4}, precision loss may occur.")
- .arg(T.x)
- .arg(T.y)
- .arg(T.z)
- .arg(maxCoordValue)
- .arg(desc.basename)
- .arg(shiftedObject->getName()));
- }
- cmd.print(QObject::tr("\t[%4 - %5] Applied Transformation {%1,%2,%3}")
- .arg(T.x)
- .arg(T.y)
- .arg(T.z)
- .arg(desc.basename)
- .arg(shiftedObject->getName()));
- transMat.toIdentity();
- transMat.setTranslation(T);
- shiftedObject->applyGLTransformation_recursive(&transMat);
- }
- //apply new global shift
- shiftedObject->setGlobalShift(newShift.x, newShift.y, newShift.z);
- cmd.print(QObject::tr("\t[%4 - %5] Global shift set to {%1,%2,%3}")
- .arg(newShift.x)
- .arg(newShift.y)
- .arg(newShift.z)
- .arg(desc.basename)
- .arg(shiftedObject->getName()));
- QString nameSuffix = QObject::tr("_SHIFTED_FROM_%1_%2_%3_TO_%4_%5_%6")
- .arg(originalShift.x)
- .arg(originalShift.y)
- .arg(originalShift.z)
- .arg(newShift.x)
- .arg(newShift.y)
- .arg(newShift.z);
- if ((&desc)->getCLEntityType() == CL_ENTITY_TYPE::MESH)
- {
- //set the mesh name instead of the vertices cloud inside the mesh
- ccHObject* parent = shiftedObject->getParent();
- if (parent)
- {
- parent->setName(QObject::tr("%1%2").arg(parent->getName()).arg(nameSuffix));
- }
- }
- else
- {
- shiftedObject->setName(QObject::tr("%1%2").arg(shiftedObject->getName()).arg(nameSuffix));
- }
- desc.basename += nameSuffix;
- //save it as well
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- return true;
- }
- CommandSetActiveSF::CommandSetActiveSF()
- : ccCommandLineInterface::Command(QObject::tr("Set active SF"), COMMAND_SET_ACTIVE_SF)
- {}
- bool CommandSetActiveSF::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: scalar field index after \"-%1\"").arg(COMMAND_SET_ACTIVE_SF));
- }
- int sfIndex = -1;
- QString sfName;
- if (!GetSFIndexOrName(cmd, sfIndex, sfName, true))
- {
- return false;
- }
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No point cloud nor mesh loaded! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_SET_ACTIVE_SF));
- }
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- int thisSFIndex = GetScalarFieldIndex(desc.pc, sfIndex, sfName, false);
- desc.pc->setCurrentScalarField(thisSFIndex);
- }
- }
- for (CLMeshDesc& desc : cmd.meshes())
- {
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(desc.mesh);
- if (pc)
- {
- int thisSFIndex = GetScalarFieldIndex(pc, sfIndex, sfName, false);
- pc->setCurrentScalarField(thisSFIndex);
- }
- }
- return true;
- }
- CommandRemoveAllSFs::CommandRemoveAllSFs()
- : ccCommandLineInterface::Command(QObject::tr("Remove all SF"), COMMAND_REMOVE_ALL_SFS)
- {}
- bool CommandRemoveAllSFs::process(ccCommandLineInterface& cmd)
- {
- //no argument required
- for (auto& desc : cmd.clouds())
- {
- if (desc.pc/* && desc.pc->hasScalarFields()*/)
- {
- desc.pc->deleteAllScalarFields();
- desc.pc->showSF(false);
- }
- }
-
- for (auto& desc : cmd.meshes())
- {
- if (desc.mesh)
- {
- ccGenericPointCloud* cloud = desc.mesh->getAssociatedCloud();
- if (cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- static_cast<ccPointCloud*>(cloud)->deleteAllScalarFields();
- cloud->showSF(false);
- }
- }
- }
-
- return true;
- }
- CommandRemoveSF::CommandRemoveSF()
- : ccCommandLineInterface::Command(QObject::tr("Remove a specific SF"), COMMAND_REMOVE_SF)
- {}
- bool CommandRemoveSF::removeSF(int sfIndex, ccPointCloud& pc)
- {
- if (sfIndex < static_cast<int>(pc.getNumberOfScalarFields()))
- {
- ccLog::Print("[REMOVE_SF] " + QString::fromStdString(pc.getScalarFieldName(sfIndex)));
- pc.deleteScalarField(sfIndex);
- if (pc.getNumberOfScalarFields() == 0)
- {
- pc.showSF(false);
- }
- else if (pc.getCurrentDisplayedScalarFieldIndex() < 0)
- {
- pc.setCurrentDisplayedScalarField(static_cast<int>(pc.getNumberOfScalarFields()) - 1);
- }
- return true;
- }
- else
- {
- return false;
- }
- }
- bool CommandRemoveSF::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: SF index after %1").arg(COMMAND_REMOVE_SF));
- }
- int sfIndex = -1;
- QString sfName;
- if (!GetSFIndexOrName(cmd, sfIndex, sfName, true))
- {
- return false;
- }
- for (auto& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- int thisSFIndex = GetScalarFieldIndex(desc.pc, sfIndex, sfName, true);
- if (thisSFIndex >= 0)
- {
- removeSF(thisSFIndex, *desc.pc);
- }
- }
- }
- for (auto& desc : cmd.meshes())
- {
- if (desc.mesh)
- {
- ccGenericPointCloud* cloud = desc.mesh->getAssociatedCloud();
- if (cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
- int thisSFIndex = GetScalarFieldIndex(pc, sfIndex, sfName, true);
- if (thisSFIndex >= 0)
- {
- removeSF(thisSFIndex, *pc);
- if (pc->getNumberOfScalarFields() == 0)
- {
- desc.mesh->showSF(false);
- }
- }
- }
- }
- }
- return true;
- }
- CommandRemoveRGB::CommandRemoveRGB()
- : ccCommandLineInterface::Command(QObject::tr("Remove RGB"), COMMAND_REMOVE_RGB)
- {}
- bool CommandRemoveRGB::process(ccCommandLineInterface& cmd)
- {
- //no argument required
- for (auto& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- desc.pc->unallocateColors();
- desc.pc->showColors(false);
- }
- }
- for (auto& desc : cmd.meshes())
- {
- if (desc.mesh)
- {
- ccGenericPointCloud* cloud = desc.mesh->getAssociatedCloud();
- if (cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- static_cast<ccPointCloud*>(cloud)->unallocateColors();
- cloud->showColors(false);
- }
- desc.mesh->showColors(false);
- }
- }
- return true;
- }
- CommandRemoveNormals::CommandRemoveNormals()
- : ccCommandLineInterface::Command(QObject::tr("Remove normals"), COMMAND_REMOVE_NORMALS)
- {}
- bool CommandRemoveNormals::process(ccCommandLineInterface& cmd)
- {
- //no argument required
- for (auto& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- desc.pc->unallocateNorms();
- desc.pc->showNormals(false);
- }
- }
- for (auto& desc : cmd.meshes())
- {
- if (desc.mesh)
- {
- ccGenericPointCloud* cloud = desc.mesh->getAssociatedCloud();
- if (cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- static_cast<ccPointCloud*>(cloud)->unallocateNorms();
- cloud->showNormals(false);
- }
- if (desc.mesh->isA(CC_TYPES::MESH))
- {
- static_cast<ccMesh*>(desc.mesh)->clearTriNormals();
- desc.mesh->showNormals(false);
- }
- }
- }
- return true;
- }
- CommandRemoveScanGrids::CommandRemoveScanGrids()
- : ccCommandLineInterface::Command(QObject::tr("Remove scan grids"), COMMAND_REMOVE_SCAN_GRIDS)
- {}
- bool CommandRemoveScanGrids::process(ccCommandLineInterface& cmd)
- {
- //no argument required
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- desc.pc->removeGrids();
- }
- }
-
- for (CLMeshDesc& desc : cmd.meshes())
- {
- if (desc.mesh)
- {
- ccGenericPointCloud* cloud = desc.mesh->getAssociatedCloud();
- if (cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- static_cast<ccPointCloud*>(cloud)->removeGrids();
- }
- }
- }
-
- return true;
- }
- CommandRemoveSensors::CommandRemoveSensors()
- : ccCommandLineInterface::Command(QObject::tr("Remove sensors"), COMMAND_REMOVE_SENSORS)
- {}
- static void RemoveSensors(ccHObject* entity)
- {
- if (!entity)
- {
- assert(false);
- return;
- }
- ccHObject::Container sensors;
- entity->filterChildren(sensors, false, CC_TYPES::SENSOR, false); // direct children only
- for (ccHObject* sensor : sensors)
- {
- ccLog::Print(QString("Removing sensor '%1' from entity '%2'").arg(sensor->getName()).arg(entity->getName()));
- entity->removeChild(sensor);
- }
- }
- bool CommandRemoveSensors::process(ccCommandLineInterface& cmd)
- {
- //no argument required
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- RemoveSensors(desc.pc);
- }
- }
- for (CLMeshDesc& desc : cmd.meshes())
- {
- if (desc.mesh)
- {
- RemoveSensors(desc.mesh);
- ccGenericPointCloud* cloud = desc.mesh->getAssociatedCloud();
- RemoveSensors(cloud);
- }
- }
- return true;
- }
- CommandMatchBBCenters::CommandMatchBBCenters()
- : ccCommandLineInterface::Command(QObject::tr("Match B.B. centers"), COMMAND_MATCH_BB_CENTERS)
- {}
- bool CommandMatchBBCenters::process(ccCommandLineInterface& cmd)
- {
- std::vector<CLEntityDesc*> entities;
- for (auto& cloud : cmd.clouds())
- {
- entities.push_back(&cloud);
- }
- for (auto& mesh : cmd.meshes())
- {
- entities.push_back(&mesh);
- }
-
- if (entities.empty())
- {
- return cmd.error("No entity loaded!");
- }
- else if (entities.size() == 1)
- {
- cmd.warning("Nothing to do: only one entity currently loaded!");
- return true;
- }
-
- CCVector3 firstCenter = entities.front()->getEntity()->getOwnBB().getCenter();
- for (size_t i = 1; i < entities.size(); ++i)
- {
- ccHObject* ent = entities[i]->getEntity();
- CCVector3 center = ent->getOwnBB().getCenter();
- CCVector3 T = firstCenter - center;
-
- //transformation (used only for translation)
- ccGLMatrix glTrans;
- glTrans += T;
-
- //apply translation matrix
- ent->applyGLTransformation_recursive(&glTrans);
- cmd.print(QObject::tr("Entity '%1' has been translated: (%2,%3,%4)").arg(ent->getName()).arg(T.x).arg(T.y).arg(T.z));
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(*entities[i]);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
-
- return true;
- }
- CommandMatchBestFitPlane::CommandMatchBestFitPlane()
- : ccCommandLineInterface::Command(QObject::tr("Compute best fit plane"), COMMAND_BEST_FIT_PLANE)
- {}
- bool CommandMatchBestFitPlane::process(ccCommandLineInterface& cmd)
- {
- //look for local options
- bool makeCloudsHoriz = false;
- bool keepLoaded = false;
-
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_BEST_FIT_PLANE_MAKE_HORIZ))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- makeCloudsHoriz = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_BEST_FIT_PLANE_KEEP_LOADED))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- keepLoaded = true;
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
-
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No cloud available. Be sure to open one first!"));
- }
-
- for (CLCloudDesc& desc : cmd.clouds())
- {
- //try to fit plane
- double rms = 0.0;
- ccPlane* pPlane = ccPlane::Fit(desc.pc, &rms);
- if (pPlane)
- {
- cmd.print(QObject::tr("Plane successfully fitted: rms = %1").arg(rms));
-
- CCVector3 N = pPlane->getNormal();
- CCVector3 C = *CCCoreLib::Neighbourhood(desc.pc).getGravityCenter();
-
- CLMeshDesc planeDesc;
- planeDesc.mesh = pPlane;
- planeDesc.basename = desc.basename;
- planeDesc.path = desc.path;
-
- //save plane as a BIN file
- QString outputFilename;
- QString errorStr = cmd.exportEntity(planeDesc, "BEST_FIT_PLANE", &outputFilename);
- if (!errorStr.isEmpty())
- {
- cmd.warning(errorStr);
- }
-
- //compute the transformation matrix that would make this normal points towards +Z
- ccGLMatrix makeZPosMatrix = ccGLMatrix::FromToRotation(N, CCVector3(0, 0, CCCoreLib::PC_ONE));
- CCVector3 Gt = C;
- makeZPosMatrix.applyRotation(Gt);
- makeZPosMatrix.setTranslation(C - Gt);
- //open text file to save plane related information
- {
- QString txtFilename = QObject::tr("%1/%2_BEST_FIT_PLANE_INFO").arg(desc.path, desc.basename);
- if (cmd.addTimestamp())
- {
- QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd_hh'h'mm_ss_zzz");
- txtFilename += QObject::tr("_%1").arg(timestamp);
- }
- txtFilename += QObject::tr(".txt");
- QFile txtFile(txtFilename);
- if (txtFile.open(QIODevice::WriteOnly | QIODevice::Text))
- {
- QTextStream txtStream(&txtFile);
- txtStream << QObject::tr("Filename: %1").arg(outputFilename) << endl;
- txtStream << QObject::tr("Fitting RMS: %1").arg(rms) << endl;
- //We always consider the normal with a positive 'Z' by default!
- if (N.z < 0.0)
- {
- N *= -1.0;
- }
- int precision = cmd.numericalPrecision();
- txtStream << QObject::tr("Normal: (%1,%2,%3)").arg(N.x, 0, 'f', precision).arg(N.y, 0, 'f', precision).arg(N.z, 0, 'f', precision) << endl;
- //we compute strike & dip by the way
- {
- PointCoordinateType dip = 0;
- PointCoordinateType dipDir = 0;
- ccNormalVectors::ConvertNormalToDipAndDipDir(N, dip, dipDir);
- txtStream << ccNormalVectors::ConvertDipAndDipDirToString(dip, dipDir) << endl;
- }
- txtStream << "Orientation matrix:" << endl;
- txtStream << makeZPosMatrix.toString(precision, ' ') << endl;
- //close the text file
- txtFile.close();
- }
- else
- {
- cmd.warning("Failed to open file " + txtFilename + " for writing");
- }
- }
-
- if (keepLoaded)
- {
- //add the resulting plane (mesh) to the main set
- cmd.meshes().push_back(planeDesc);
- }
-
- if (makeCloudsHoriz)
- {
- //apply 'horizontal' matrix
- desc.pc->applyGLTransformation_recursive(&makeZPosMatrix);
- cmd.print(QObject::tr("Cloud '%1' has been transformed with the above matrix").arg(desc.pc->getName()));
- desc.basename += QObject::tr("_HORIZ");
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- cmd.warning(errorStr);
- }
- }
- }
- }
- else
- {
- cmd.warning(QObject::tr("Failed to compute best fit plane for cloud '%1'").arg(desc.pc->getName()));
- }
- }
-
- return true;
- }
- CommandOrientNormalsMST::CommandOrientNormalsMST()
- : ccCommandLineInterface::Command(QObject::tr("Orient normals"), COMMAND_ORIENT_NORMALS)
- {}
- bool CommandOrientNormalsMST::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number of neighbors after \"-%1\"").arg(COMMAND_ORIENT_NORMALS));
- }
-
- QString knnStr = cmd.arguments().takeFirst();
- bool ok;
- int knn = knnStr.toInt(&ok);
- if (!ok || knn <= 0)
- {
- return cmd.error(QObject::tr("Invalid parameter: number of neighbors (%1)").arg(knnStr));
- }
-
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No cloud available. Be sure to open one first!"));
- }
-
- QScopedPointer<ccProgressDialog> progressDialog(nullptr);
- if (!cmd.silentMode())
- {
- progressDialog.reset(new ccProgressDialog(false, cmd.widgetParent()));
- progressDialog->setAutoClose(false);
- }
-
- for (CLCloudDesc& desc : cmd.clouds())
- {
- assert(desc.pc);
-
- if (!desc.pc->hasNormals())
- {
- continue;
- }
-
- //computation
- if (desc.pc->orientNormalsWithMST(knn, progressDialog.data()))
- {
- desc.basename += QObject::tr("_NORMS_REORIENTED");
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- cmd.warning(errorStr);
- }
- }
- }
- else
- {
- return cmd.error(QObject::tr("Failed to orient the normals of cloud '%1'!").arg(desc.pc->getName()));
- }
- }
-
- if (progressDialog)
- {
- progressDialog->close();
- QCoreApplication::processEvents();
- }
-
- return true;
- }
- CommandSORFilter::CommandSORFilter()
- : ccCommandLineInterface::Command(QObject::tr("S.O.R. filter"), COMMAND_SOR_FILTER)
- {}
- bool CommandSORFilter::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number of neighbors mode after \"-%1\"").arg(COMMAND_SOR_FILTER));
- }
-
- QString knnStr = cmd.arguments().takeFirst();
- bool ok;
- int knn = knnStr.toInt(&ok);
- if (!ok || knn <= 0)
- {
- return cmd.error(QObject::tr("Invalid parameter: number of neighbors (%1)").arg(knnStr));
- }
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: sigma multiplier after number of neighbors (SOR)"));
- }
- QString sigmaStr = cmd.arguments().takeFirst();
- double nSigma = sigmaStr.toDouble(&ok);
- if (!ok || nSigma < 0)
- {
- return cmd.error(QObject::tr("Invalid parameter: sigma multiplier (%1)").arg(nSigma));
- }
-
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No cloud available. Be sure to open one first!"));
- }
-
- QScopedPointer<ccProgressDialog> progressDialog(nullptr);
- if (!cmd.silentMode())
- {
- progressDialog.reset(new ccProgressDialog(false, cmd.widgetParent()));
- progressDialog->setAutoClose(false);
- }
-
- for (CLCloudDesc& desc : cmd.clouds())
- {
- assert(desc.pc);
-
- //computation
- CCCoreLib::ReferenceCloud* selection = CCCoreLib::CloudSamplingTools::sorFilter(desc.pc,
- knn,
- nSigma,
- nullptr,
- progressDialog.data());
-
- if (selection)
- {
- ccPointCloud* cleanCloud = desc.pc->partialClone(selection);
- if (cleanCloud)
- {
- cleanCloud->setName(desc.pc->getName() + QObject::tr(".clean"));
- if (cmd.autoSaveMode())
- {
- CLCloudDesc newDesc(cleanCloud, desc.basename, desc.path, desc.indexInFile);
- QString errorStr = cmd.exportEntity(newDesc, "SOR");
- if (!errorStr.isEmpty())
- {
- delete cleanCloud;
- return cmd.error(errorStr);
- }
- }
- //replace current cloud by this one
- delete desc.pc;
- desc.pc = cleanCloud;
- desc.basename += QObject::tr("_SOR");
- //delete cleanCloud;
- //cleanCloud = 0;
- }
- else
- {
- return cmd.error(QObject::tr("Not enough memory to create a clean version of cloud '%1'!").arg(desc.pc->getName()));
- }
-
- delete selection;
- selection = nullptr;
- }
- else
- {
- //no points fall inside selection!
- return cmd.error(QObject::tr("Failed to apply SOR filter on cloud '%1'! (empty output or not enough memory?)").arg(desc.pc->getName()));
- }
- }
-
- if (progressDialog)
- {
- progressDialog->close();
- QCoreApplication::processEvents();
- }
-
- return true;
- }
- CommandNoiseFilter::CommandNoiseFilter()
- : ccCommandLineInterface::Command(QObject::tr("Noise filter"), COMMAND_NOISE_FILTER)
- {}
- bool CommandNoiseFilter::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().size() < 4)
- {
- return cmd.error(QObject::tr("Missing parameters: 'KNN/RADIUS {value} REL/ABS {value}' expected after \"-%1\"").arg(COMMAND_NOISE_FILTER));
- }
- QString firstOption = cmd.arguments().takeFirst().toUpper();
- int knn = -1;
- double radius = std::numeric_limits<double>::quiet_NaN();
- if (firstOption == COMMAND_NOISE_FILTER_KNN)
- {
- QString knnStr = cmd.arguments().takeFirst();
- bool ok;
- knn = knnStr.toInt(&ok);
- if (!ok || knn <= 0)
- {
- return cmd.error(QObject::tr("Invalid parameter: number of neighbors after KNN (got '%1' instead)").arg(knnStr));
- }
- }
- else if(firstOption == COMMAND_NOISE_FILTER_RADIUS)
- {
- QString radiusStr = cmd.arguments().takeFirst();
- bool ok;
- radius = radiusStr.toDouble(&ok);
- if (!ok || radius <= 0)
- {
- return cmd.error(QObject::tr("Invalid parameter: radius after RADIUS (got '%1' instead)").arg(radiusStr));
- }
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: KNN or RADIUS expected after \"-%1\"").arg(COMMAND_NOISE_FILTER));
- }
- QString secondOption = cmd.arguments().takeFirst().toUpper();
- bool absoluteError = true;
- if (secondOption == COMMAND_NOISE_FILTER_REL)
- {
- absoluteError = false;
- }
- else if (secondOption == COMMAND_NOISE_FILTER_ABS)
- {
- absoluteError = true;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: REL or ABS expected"));
- }
- double error = std::numeric_limits<double>::quiet_NaN();
- {
- QString errorStr = cmd.arguments().takeFirst();
- bool ok;
- error = errorStr.toDouble(&ok);
- if (!ok || error <= 0)
- {
- return cmd.error(QObject::tr("Invalid parameter: relative or absolute error expected after KNN (got '%1' instead)").arg(errorStr));
- }
- }
- //optional arguments
- bool removeIsolatedPoints = false;
- if (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (argument == COMMAND_NOISE_FILTER_RIP)
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- removeIsolatedPoints = true;
- }
- }
- QScopedPointer<ccProgressDialog> progressDialog(nullptr);
- if (!cmd.silentMode())
- {
- progressDialog.reset(new ccProgressDialog(false, cmd.widgetParent()));
- progressDialog->setAutoClose(false);
- }
- for (CLCloudDesc& desc : cmd.clouds())
- {
- assert(desc.pc);
- //computation
- CCCoreLib::ReferenceCloud* selection = CCCoreLib::CloudSamplingTools::noiseFilter(desc.pc,
- static_cast<PointCoordinateType>(radius),
- error,
- removeIsolatedPoints,
- knn > 0,
- knn,
- absoluteError,
- error,
- nullptr,
- progressDialog.data());
- if (selection)
- {
- ccPointCloud* cleanCloud = desc.pc->partialClone(selection);
- if (cleanCloud)
- {
- cleanCloud->setName(desc.pc->getName() + QObject::tr(".clean"));
- if (cmd.autoSaveMode())
- {
- CLCloudDesc newDesc(cleanCloud, desc.basename, desc.path, desc.indexInFile);
- QString errorStr = cmd.exportEntity(newDesc, "DENOISED");
- if (!errorStr.isEmpty())
- {
- delete cleanCloud;
- return cmd.error(errorStr);
- }
- }
- //replace current cloud by this one
- delete desc.pc;
- desc.pc = cleanCloud;
- desc.basename += QObject::tr("_DENOISED");
- //delete cleanCloud;
- //cleanCloud = 0;
- }
- else
- {
- return cmd.error(QObject::tr("Not enough memory to create a clean version of cloud '%1'!").arg(desc.pc->getName()));
- }
- delete selection;
- selection = nullptr;
- }
- else
- {
- //no points fall inside selection!
- return cmd.error(QObject::tr("Failed to apply Noise filter on cloud '%1'! (empty output or not enough memory?)").arg(desc.pc->getName()));
- }
- }
- if (progressDialog)
- {
- progressDialog->close();
- QCoreApplication::processEvents();
- }
- return true;
- }
- CommandRemoveDuplicatePoints::CommandRemoveDuplicatePoints()
- : ccCommandLineInterface::Command(QObject::tr("Remove duplicate points"), COMMAND_REMOVE_DUPLICATE_POINTS)
- {}
- bool CommandRemoveDuplicatePoints::process(ccCommandLineInterface& cmd)
- {
- double minDistanceBetweenPoints = std::numeric_limits<double>::epsilon();
- //get optional argument
- if (!cmd.arguments().empty())
- {
- bool paramOk = false;
- double arg = cmd.arguments().front().toDouble(¶mOk);
- if (paramOk)
- {
- if (arg < minDistanceBetweenPoints)
- {
- return cmd.error(QObject::tr("Invalid argument: '%1'").arg(arg));
- }
- minDistanceBetweenPoints = arg;
- cmd.arguments().pop_front();
- }
- }
- cmd.print(QObject::tr("Minimum distance between points: '%1'").arg(minDistanceBetweenPoints));
- QScopedPointer<ccProgressDialog> progressDialog(nullptr);
- if (!cmd.silentMode())
- {
- progressDialog.reset(new ccProgressDialog(false, cmd.widgetParent()));
- progressDialog->setAutoClose(false);
- }
- for (CLCloudDesc& desc : cmd.clouds())
- {
- assert(desc.pc);
- ccPointCloud* filteredCloud = desc.pc->removeDuplicatePoints(minDistanceBetweenPoints, progressDialog.data());
- if (!filteredCloud)
- {
- return cmd.error(QObject::tr("Process failed (see log)"));
- }
- if (filteredCloud == desc.pc)
- {
- // nothing to do
- continue;
- }
- //replace current cloud by filtered one
- delete desc.pc;
- desc.pc = filteredCloud;
- desc.basename += QObject::tr("_REMOVED_DUPLICATE_POINTS");
- }
- if (progressDialog)
- {
- progressDialog->close();
- QCoreApplication::processEvents();
- }
- return true;
- }
- CommandExtractVertices::CommandExtractVertices()
- : ccCommandLineInterface::Command(QObject::tr("Extract vertices"), COMMAND_EXTRACT_VERTICES)
- {}
- bool CommandExtractVertices::process(ccCommandLineInterface& cmd)
- {
- if (cmd.meshes().empty())
- {
- cmd.warning(QObject::tr("No mesh available. Be sure to open one first!"));
- return false;
- }
-
- for (CLMeshDesc& desc : cmd.meshes())
- {
- ccGenericMesh* mesh = desc.mesh;
- ccGenericPointCloud* cloud = mesh->getAssociatedCloud();
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(cloud);
- if (!pc)
- {
- assert(false);
- continue;
- }
-
- //add the resulting cloud to the main set
- cmd.clouds().emplace_back(pc, desc.basename + QObject::tr(".vertices"), desc.path);
-
- //don't forget to detach the cloud before we delete the meshes!
- assert(pc->getParent() == mesh);
- if (pc->getParent())
- {
- pc->getParent()->detachChild(pc);
- }
-
- //save it as well
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(cmd.clouds().back());
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
-
- cmd.removeMeshes(false);
-
- return true;
- }
- CommandFlipTriangles::CommandFlipTriangles()
- : ccCommandLineInterface::Command(QObject::tr("Flip mesh triangles"), COMMAND_FLIP_TRIANGLES)
- {}
- bool CommandFlipTriangles::process(ccCommandLineInterface& cmd)
- {
- if (cmd.meshes().empty())
- {
- cmd.warning(QObject::tr("No mesh available. Be sure to open one first!"));
- return false;
- }
- for (CLMeshDesc& desc : cmd.meshes())
- {
- ccGenericMesh* mesh = desc.mesh;
- ccMesh* ccMesh = ccHObjectCaster::ToMesh(mesh);
- if (ccMesh)
- {
- ccMesh->flipTriangles();
- }
- desc.basename += QObject::tr("_FLIPPED_TRIANGLES");
- //save it as well
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- cmd.removeMeshes(false);
- return true;
- }
- CommandSampleMesh::CommandSampleMesh()
- : ccCommandLineInterface::Command(QObject::tr("Sample mesh"), COMMAND_SAMPLE_MESH)
- {}
- bool CommandSampleMesh::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: sampling mode after \"-%1\" (POINTS/DENSITY)").arg(COMMAND_SAMPLE_MESH));
- }
-
- bool useDensity = false;
- double parameter = 0;
-
- QString sampleMode = cmd.arguments().takeFirst().toUpper();
- if (sampleMode == "POINTS")
- {
- useDensity = false;
- }
- else if (sampleMode == "DENSITY")
- {
- useDensity = true;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: unknown sampling mode \"%1\"").arg(sampleMode));
- }
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: value after sampling mode"));
- }
- bool conversionOk = false;
- parameter = cmd.arguments().takeFirst().toDouble(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: value after sampling mode"));
- }
-
- if (cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No mesh available. Be sure to open one first!"));
- }
-
- QScopedPointer<ccProgressDialog> progressDialog(nullptr);
- if (!cmd.silentMode())
- {
- progressDialog.reset(new ccProgressDialog(false, cmd.widgetParent()));
- progressDialog->setAutoClose(false);
- }
-
- for (CLMeshDesc& desc : cmd.meshes())
- {
- ccPointCloud* cloud = desc.mesh->samplePoints(useDensity, parameter, true, true, true, progressDialog.data());
-
- if (!cloud)
- {
- return cmd.error(QObject::tr("Cloud sampling failed!"));
- }
-
- //add the resulting cloud to the main set
- cmd.print(QObject::tr("Sampled cloud created: %1 points").arg(cloud->size()));
- cmd.clouds().emplace_back(cloud, desc.basename + QObject::tr("_SAMPLED_POINTS"), desc.path);
-
- //save it as well
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(cmd.clouds().back());
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
-
- if (progressDialog)
- {
- progressDialog->close();
- QCoreApplication::processEvents();
- }
-
- return true;
- }
- CommandCompressFWF::CommandCompressFWF()
- : ccCommandLineInterface::Command(QObject::tr("Compress FWF"), COMMAND_COMPRESS_FWF)
- {}
- bool CommandCompressFWF::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud available. Be sure to open or generate one first!"));
- }
- for (CLCloudDesc& desc : cmd.clouds())
- {
- desc.pc->compressFWFData();
- }
- return true;
- }
- CommandCrop::CommandCrop()
- : ccCommandLineInterface::Command(QObject::tr("Crop"), COMMAND_CROP)
- {}
- bool CommandCrop::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: box extents after \"-%1\" (Xmin:Ymin:Zmin:Xmax:Ymax:Zmax)").arg(COMMAND_CROP));
- }
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No point cloud or mesh available. Be sure to open or generate one first!"));
- }
-
- //decode box extents
- CCVector3 boxMin;
- CCVector3 boxMax;
- {
- QString boxBlock = cmd.arguments().takeFirst();
- QStringList tokens = boxBlock.split(':');
- if (tokens.size() != 6)
- {
- return cmd.error(QObject::tr("Invalid parameter: box extents (expected format is 'Xmin:Ymin:Zmin:Xmax:Ymax:Zmax')").arg(COMMAND_CROP));
- }
-
- for (int i = 0; i < 6; ++i)
- {
- CCVector3* vec = (i < 3 ? &boxMin : &boxMax);
- bool ok = true;
- vec->u[i % 3] = static_cast<PointCoordinateType>(tokens[i].toDouble(&ok));
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid parameter: box extents (component #%1 is not a valid number)").arg(i + 1));
- }
- }
- }
-
- //optional parameters
- bool inside = true;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_CROP_OUTSIDE))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- inside = false;
- }
- else
- {
- break;
- }
- }
-
- ccBBox cropBox(boxMin, boxMax, true);
- //crop clouds
- {
- for (CLCloudDesc& desc : cmd.clouds())
- {
- ccHObject* croppedCloud = ccCropTool::Crop(desc.pc, cropBox, inside);
- if (croppedCloud)
- {
- delete desc.pc;
- assert(croppedCloud->isA(CC_TYPES::POINT_CLOUD));
- desc.pc = static_cast<ccPointCloud*>(croppedCloud);
- desc.basename += "_CROPPED";
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- else
- {
- //otherwise an error message has already been issued
- delete desc.pc;
- desc.pc = nullptr; //will be removed after this loop
- //desc.basename += "_FULLY_CROPPED";
- }
- }
- //now clean the set of clouds in case some have been 'cropped out'
- for (auto it = cmd.clouds().begin(); it != cmd.clouds().end(); )
- {
- if (it->pc == nullptr)
- {
- it = cmd.clouds().erase(it);
- }
- else
- {
- ++it;
- }
- }
- }
-
- //crop meshes
- {
- for (CLMeshDesc& desc : cmd.meshes())
- {
- ccHObject* croppedMesh = ccCropTool::Crop(desc.mesh, cropBox, inside);
- if (croppedMesh)
- {
- delete desc.mesh;
- assert(croppedMesh->isA(CC_TYPES::MESH));
- desc.mesh = static_cast<ccMesh*>(croppedMesh);
- desc.basename += "_CROPPED";
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- else
- {
- //otherwise an error message has already been issued
- delete desc.mesh;
- desc.mesh = nullptr; //will be removed after this loop
- //desc.basename += "_FULLY_CROPPED";
- }
- }
- //now clean the set of meshes in case some have been 'cropped out'
- for (auto it = cmd.meshes().begin(); it != cmd.meshes().end(); )
- {
- if (it->mesh == nullptr)
- {
- it = cmd.meshes().erase(it);
- }
- else
- {
- ++it;
- }
- }
- }
-
- return true;
- }
- CommandSFToCoord::CommandSFToCoord()
- : ccCommandLineInterface::Command(QObject::tr("SF to Coord"), COMMAND_SF_TO_COORD)
- {}
- bool CommandSFToCoord::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().size() < 2)
- {
- return cmd.error(QObject::tr("Missing parameter(s) after \"-%1\" (SF INDEX OR NAME) (DIMENSION)").arg(COMMAND_SF_TO_COORD));
- }
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud available. Be sure to open or generate one first!"));
- }
- int sfIndex = -1;
- QString sfName;
- if (!GetSFIndexOrName(cmd, sfIndex, sfName))
- {
- return false;
- }
- //dimension
- QString dimStr = cmd.arguments().takeFirst().toUpper();
- bool exportDims[3] { dimStr == "X", dimStr == "Y", dimStr == "Z" };
- if (!exportDims[0] && !exportDims[1] && !exportDims[2])
- {
- return cmd.error(QObject::tr("Invalid parameter: dimension after \"-%1\" (expected: X, Y or Z)").arg(COMMAND_SF_TO_COORD));
- }
- //now we can export the corresponding coordinate
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- CCCoreLib::ScalarField* sf = GetScalarField(desc.pc, sfIndex, sfName, true);
- if (sf)
- {
- if (desc.pc->setCoordFromSF(exportDims, sf, std::numeric_limits<PointCoordinateType>::quiet_NaN()))
- {
- desc.basename += QObject::tr("_SF_TO_COORD_%1").arg(dimStr);
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- else
- {
- return cmd.error(QObject::tr("Failed to set SF %1 as coord %2 on cloud '%3'!").arg(sfName).arg(dimStr).arg(desc.pc->getName()));
- }
- }
- }
- return true;
- }
- CommandCoordToSF::CommandCoordToSF()
- : ccCommandLineInterface::Command(QObject::tr("Coord to SF"), COMMAND_COORD_TO_SF)
- {}
- bool CommandCoordToSF::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter after \"-%1\" (DIMENSION)").arg(COMMAND_COORD_TO_SF));
- }
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud available. Be sure to open or generate one first!"));
- }
-
- //dimension
- QString dimStr = cmd.arguments().takeFirst().toUpper();
- bool exportDims[3]{ dimStr == "X", dimStr == "Y", dimStr == "Z" };
- if (!exportDims[0] && !exportDims[1] && !exportDims[2])
- {
- return cmd.error(QObject::tr("Invalid parameter: dimension after \"-%1\" (expected: X, Y or Z)").arg(COMMAND_COORD_TO_SF));
- }
- //now we can export the corresponding coordinate
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc->exportCoordToSF(exportDims))
- {
- desc.basename += QObject::tr("_%1_TO_SF").arg(dimStr);
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- else
- {
- return cmd.error(QObject::tr("Failed to export coord. %1 to SF on cloud '%2'!").arg(dimStr, desc.pc->getName()));
- }
- }
-
- return true;
- }
- CommandCrop2D::CommandCrop2D()
- : ccCommandLineInterface::Command(QObject::tr("Crop 2D"), COMMAND_CROP_2D)
- {}
- bool CommandCrop2D::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().size() < 6)
- {
- return cmd.error(QObject::tr("Missing parameter(s) after \"-%1\" (ORTHO_DIM N X1 Y1 X2 Y2 ... XN YN)").arg(COMMAND_CROP_2D));
- }
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud available. Be sure to open or generate one first!"));
- }
-
- //decode poyline extents
- ccPointCloud vertices("polyline.vertices");
- ccPolyline poly(&vertices);
-
- //orthogonal dimension
- unsigned char orthoDim = 2;
- bool orderFlipped = false;
- {
- QString orthoDimStr = cmd.arguments().takeFirst().toUpper();
- if (orthoDimStr.endsWith("FLIP"))
- {
- orderFlipped = true;
- orthoDimStr = orthoDimStr.left(orthoDimStr.size() - 4);
- }
- if (orthoDimStr == "X")
- {
- orthoDim = 0;
- }
- else if (orthoDimStr == "Y")
- {
- orthoDim = 1;
- }
- else if (orthoDimStr == "Z")
- {
- orthoDim = 2;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: orthogonal dimension after \"-%1\" (expected: X, Y or Z)").arg(COMMAND_CROP_2D));
- }
- }
- ccCommandLineInterface::GlobalShiftOptions globalShiftOptions;
- globalShiftOptions.mode = ccCommandLineInterface::GlobalShiftOptions::NO_GLOBAL_SHIFT;
- if (cmd.arguments().size() >= 4)
- {
- if (cmd.nextCommandIsGlobalShift())
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (!cmd.processGlobalShiftCommand(globalShiftOptions))
- {
- //error message already issued
- return false;
- }
- cmd.setGlobalShiftOptions(globalShiftOptions);
- }
- }
-
- //number of vertices
- bool ok = true;
- unsigned N = 0;
- {
- QString countStr = cmd.arguments().takeFirst();
- N = countStr.toUInt(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid parameter: number of vertices for the 2D polyline after \"-%1\"").arg(COMMAND_CROP_2D));
- }
- }
-
- //now read the vertices
- {
- if (!vertices.reserve(N) || !poly.addPointIndex(0, N))
- {
- return cmd.error(QObject::tr("Not enough memory!"));
- }
- assert(orthoDim < 3);
- unsigned char X = ((orthoDim + 1) % 3);
- unsigned char Y = ((X + 1) % 3);
- unsigned char Xread = X;
- unsigned char Yread = Y;
- if (orderFlipped)
- {
- std::swap(Xread, Yread);
- }
- CCVector3d PShift(0, 0, 0);
- for (unsigned i = 0; i < N; ++i)
- {
- if (cmd.arguments().size() < 2)
- {
- return cmd.error(QObject::tr("Missing parameter(s): vertex #%1 data and following").arg(i + 1));
- }
-
- CCVector3d Pd(0, 0, 0);
-
- QString coordStr = cmd.arguments().takeFirst();
- Pd.u[Xread] = coordStr.toDouble(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid parameter: X-coordinate of vertex #%1").arg(i + 1));
- }
- /*QString */coordStr = cmd.arguments().takeFirst();
- Pd.u[Yread] = coordStr.toDouble(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid parameter: Y-coordinate of vertex #%1").arg(i + 1));
- }
- if (i == 0)
- {
- bool preserveCoordinateShift = false; //ignored
- if (FileIOFilter::HandleGlobalShift(Pd, PShift, preserveCoordinateShift, cmd.fileLoadingParams()))
- {
- ccLog::Warning(QString("[%1] 2D polyline has been recentered! Translation: (%2 ; %3 ; %4)").arg(COMMAND_CROP_2D).arg(PShift.x, 0, 'f', 2).arg(PShift.y, 0, 'f', 2).arg(PShift.z, 0, 'f', 2));
- }
- }
- CCVector3 P3D = (Pd + PShift).toPC();
- // warning: the polyline must be defined in the XY plane!
- CCVector3 P2D;
- {
- P2D.x = P3D.u[X];
- P2D.y = P3D.u[Y];
- P2D.z = 0;
- }
- vertices.addPoint(P2D);
- }
-
- poly.setClosed(true);
- }
- cmd.updateInteralGlobalShift(globalShiftOptions);
-
- //optional parameters
- bool inside = true;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_CROP_OUTSIDE))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- inside = false;
- }
- else
- {
- break;
- }
- }
-
- //now we can crop the loaded cloud(s)
- for (CLCloudDesc& desc : cmd.clouds())
- {
- CCCoreLib::ReferenceCloud* ref = desc.pc->crop2D(&poly, orthoDim, inside);
- if (ref)
- {
- if (ref->size() != 0)
- {
- ccPointCloud* croppedCloud = desc.pc->partialClone(ref);
- delete ref;
- ref = nullptr;
-
- if (croppedCloud)
- {
- delete desc.pc;
- desc.pc = croppedCloud;
- croppedCloud->setName(desc.pc->getName() + QObject::tr(".cropped"));
- desc.basename += "_CROPPED";
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- else
- {
- return cmd.error(QObject::tr("Not enough memory to crop cloud '%1'!").arg(desc.pc->getName()));
- }
- }
- else
- {
- delete ref;
- ref = nullptr;
- cmd.warning(QObject::tr("No point of cloud '%1' falls inside the input box!").arg(desc.pc->getName()));
- }
- }
- else
- {
- return cmd.error(QObject::tr("Crop process failed! (not enough memory)"));
- }
- }
-
- return true;
- }
- CommandColorBanding::CommandColorBanding()
- : ccCommandLineInterface::Command(QObject::tr("Color banding"), COMMAND_COLOR_BANDING)
- {}
- bool CommandColorBanding::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().size() < 2)
- {
- return cmd.error(QObject::tr("Missing parameter(s) after \"-%1\" (DIM FREQUENCY)").arg(COMMAND_COLOR_BANDING));
- }
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No entity available. Be sure to open or generate one first!"));
- }
-
- //dimension
- unsigned char dim = 2;
- QString dimStr = "Z";
- {
- dimStr = cmd.arguments().takeFirst().toUpper();
- if (dimStr == "X")
- {
- dim = 0;
- }
- else if (dimStr == "Y")
- {
- dim = 1;
- }
- else if (dimStr == "Z")
- {
- dim = 2;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: dimension after \"-%1\" (expected: X, Y or Z)").arg(COMMAND_COLOR_BANDING));
- }
- }
-
- //frequency
- bool ok = true;
- double freq = 0;
- {
- QString countStr = cmd.arguments().takeFirst();
- freq = countStr.toDouble(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid parameter: frequency after \"-%1 DIM\" (in Hz, integer value)").arg(COMMAND_COLOR_BANDING));
- }
- }
-
- //process clouds
- if (!cmd.clouds().empty())
- {
- bool hasclouds = false;
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- if (!desc.pc->setRGBColorByBanding(dim, freq))
- {
- return cmd.error(QObject::tr("Not enough memory"));
- }
- else
- {
- hasclouds = true;
- }
- }
- }
-
- //save output
- if (hasclouds && cmd.autoSaveMode() && !cmd.saveClouds(QObject::tr("COLOR_BANDING_%1_%2").arg(dimStr).arg(freq)))
- {
- return false;
- }
- }
-
- if (!cmd.meshes().empty())
- {
- bool hasMeshes = false;
- for (CLMeshDesc& desc : cmd.meshes())
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(desc.mesh);
- if (cloud)
- {
- if (!cloud->setRGBColorByBanding(dim, freq))
- {
- return cmd.error(QObject::tr("Not enough memory"));
- }
- else
- {
- desc.mesh->showColors(true);
- hasMeshes = true;
- }
- }
- else
- {
- cmd.warning(QObject::tr("Vertices of mesh '%1' are locked (they may be shared by multiple entities for instance). Can't apply the current command on them.").arg(desc.mesh->getName()));
- }
- }
-
- //save output
- if (hasMeshes && cmd.autoSaveMode() && !cmd.saveMeshes(QObject::tr("COLOR_BANDING_%1_%2").arg(dimStr).arg(freq)))
- {
- return false;
- }
- }
-
- return true;
- }
- CommandColorLevels::CommandColorLevels()
- : ccCommandLineInterface::Command(QObject::tr("Color levels"), COMMAND_COLOR_LEVELS)
- {}
- bool CommandColorLevels::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().size() < 5)
- {
- return cmd.error(QObject::tr("Missing parameter(s) after \"-%1\" (COLOR-BANDS MIN-INPUT-LEVEL MAX-INPUT-LEVEL MIN-OUTPUT-LEVEL MAX-OUTPUT-LEVEL)").arg(COMMAND_COLOR_LEVELS));
- }
- if (cmd.clouds().empty() && cmd.meshes().empty())
- {
- return cmd.error(QObject::tr("No entity available. Be sure to open or generate one first!"));
- }
- //color bands
- QString band = cmd.arguments().takeFirst().toUpper();
- bool rgb[3] { band.contains('R'), band.contains('G'), band.contains('B') };
- {
- QString testBand = band;
- testBand.remove('R');
- testBand.remove('G');
- testBand.remove('B');
- if (!testBand.isEmpty())
- {
- return cmd.error(QObject::tr("Invalid parameter: bands after \"-%1\" (expected: any combination of R, G or B)").arg(COMMAND_COLOR_LEVELS));
- }
- }
- //min level
- int levels[4] = { 0 };
- for (int i = 0; i < 4; ++i)
- {
- bool ok = true;
- QString levelStr = cmd.arguments().takeFirst();
- levels[i] = levelStr.toInt(&ok);
- if (!ok || levels[i] < 0 || levels[i] > 255)
- {
- return cmd.error(QObject::tr("Invalid parameter: color level after \"-%1 COLOR-BANDS\" (integer value between 0 and 255 expected)").arg(COMMAND_COLOR_LEVELS));
- }
- }
- //process clouds
- if (!cmd.clouds().empty())
- {
- bool hasClouds = false;
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc && desc.pc->hasColors())
- {
- if (!ccColorLevelsDlg::ScaleColorFields(desc.pc, levels[0], levels[1], levels[2], levels[3], rgb))
- {
- cmd.warning(QObject::tr("Failed to scale the color band(s) of cloud '%1'").arg(desc.pc->getName()));
- }
- else
- {
- hasClouds = true;
- }
- }
- }
- //save output
- if (hasClouds && cmd.autoSaveMode() && !cmd.saveClouds(QObject::tr("COLOR_LEVELS_%1_%2_%3").arg(band).arg(levels[2]).arg(levels[3])))
- {
- return false;
- }
- }
- if (!cmd.meshes().empty())
- {
- bool hasMeshes = false;
- for (CLMeshDesc& desc : cmd.meshes())
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(desc.mesh);
- if (cloud && cloud->hasColors())
- {
- if (!ccColorLevelsDlg::ScaleColorFields(cloud, levels[0], levels[1], levels[2], levels[3], rgb))
- {
- cmd.warning(QObject::tr("Failed to scale the color band(s) of mesh '%1'").arg(desc.mesh->getName()));
- }
- else
- {
- hasMeshes = true;
- }
- }
- else if (desc.mesh->hasColors())
- {
- cmd.warning(QObject::tr("Vertices of mesh '%1' are locked (they may be shared by multiple entities for instance). Can't apply the current command on them.").arg(desc.mesh->getName()));
- }
- }
- //save output
- if (hasMeshes && cmd.autoSaveMode() && !cmd.saveMeshes(QObject::tr("COLOR_LEVELS_%1_%2_%3").arg(band).arg(levels[2]).arg(levels[3])))
- {
- return false;
- }
- }
- return true;
- }
- CommandDist::CommandDist(bool cloud2meshDist, const QString& name, const QString& keyword)
- : ccCommandLineInterface::Command(name, keyword)
- , m_cloud2meshDist(cloud2meshDist)
- {}
- bool CommandDist::process(ccCommandLineInterface& cmd)
- {
- //compared cloud
- CLEntityDesc* compEntity = nullptr;
- ccHObject* compCloud = nullptr;
- size_t nextMeshIndex = 0;
- if (cmd.clouds().empty())
- {
- //no cloud loaded
- if (!m_cloud2meshDist || cmd.meshes().size() < 2)
- {
- //we would need at least two meshes
- return cmd.error(QObject::tr("No point cloud available. Be sure to open or generate one first!"));
- }
- else
- {
- cmd.warning(QObject::tr("No point cloud available. Will use the first mesh vertices as compared cloud."));
- compEntity = &(cmd.meshes().front());
- compCloud = dynamic_cast<ccPointCloud*>(cmd.meshes()[nextMeshIndex++].mesh->getAssociatedCloud());
- if (!compCloud)
- {
- return cmd.error(QObject::tr("Unhandled mesh vertices type"));
- }
- }
- }
- else //at least two clouds
- {
- if (m_cloud2meshDist && cmd.clouds().size() != 1)
- {
- cmd.warning(QObject::tr("[C2M] Multiple point clouds loaded! Will take the first one by default."));
- }
- compEntity = &(cmd.clouds().front());
- compCloud = cmd.clouds().front().pc;
- }
- assert(compEntity && compCloud);
-
- //reference entity
- ccHObject* refEntity = nullptr;
- if (m_cloud2meshDist)
- {
- if (cmd.meshes().size() <= nextMeshIndex)
- {
- return cmd.error(QObject::tr("No mesh available. Be sure to open one first!"));
- }
- else if (cmd.meshes().size() != nextMeshIndex + 1)
- {
- cmd.warning(QString("Multiple meshes loaded! We take the %1 one by default").arg(nextMeshIndex == 0 ? "first" : "second"));
- }
- refEntity = cmd.meshes()[nextMeshIndex].mesh;
- }
- else
- {
- if (cmd.clouds().size() < 2)
- {
- return cmd.error(QObject::tr("Only one point cloud available. Be sure to open or generate a second one before performing C2C distance!"));
- }
- else if (cmd.clouds().size() > 2)
- {
- cmd.warning(QObject::tr("More than 3 point clouds loaded! We take the second one as reference by default"));
- }
- refEntity = cmd.clouds()[1].pc;
- }
-
- //inner loop for Distance computation options
- bool flipNormals = false;
- bool unsignedDistances = false;
- bool robust = true;
- double maxDist = 0.0;
- unsigned octreeLevel = 0;
- int maxThreadCount = 0;
-
- bool splitXYZ = false;
- bool mergeXY = false;
- int modelIndex = 0;
- bool useKNN = true;
- double nSize = 0;
-
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2M_DIST_FLIP_NORMALS))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- flipNormals = true;
-
- if (!m_cloud2meshDist)
- {
- cmd.warning(QObject::tr("Parameter \"-%1\" ignored: only for C2M distance!").arg(COMMAND_C2M_DIST_FLIP_NORMALS));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2M_DIST_UNSIGNED))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- unsignedDistances = true;
- if (!m_cloud2meshDist)
- {
- cmd.warning(QObject::tr("Parameter \"-%1\" ignored: only for C2M distance!").arg(COMMAND_C2M_DIST_UNSIGNED));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2M_DIST_NON_ROBUST))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- robust = false;
- if (!m_cloud2meshDist)
- {
- cmd.warning(QObject::tr("Parameter \"-%1\" ignored: only for C2M distance!").arg(COMMAND_C2M_DIST_NON_ROBUST));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2X_MAX_DISTANCE))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: value after \"-%1\"").arg(COMMAND_C2X_MAX_DISTANCE));
- }
- bool conversionOk = false;
- maxDist = cmd.arguments().takeFirst().toDouble(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_C2X_MAX_DISTANCE));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2X_OCTREE_LEVEL))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: value after \"-%1\"").arg(COMMAND_C2X_OCTREE_LEVEL));
- }
- bool conversionOk = false;
- octreeLevel = cmd.arguments().takeFirst().toUInt(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_C2X_OCTREE_LEVEL));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2C_SPLIT_XYZ))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- splitXYZ = true;
-
- if (m_cloud2meshDist)
- {
- cmd.warning(QObject::tr("Parameter \"-%1\" ignored: only for C2C distance!"));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2C_SPLIT_XY_Z))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- splitXYZ = true;
- mergeXY = true;
- if (m_cloud2meshDist)
- {
- cmd.warning(QObject::tr("Parameter \"-%1\" ignored: only for C2C distance!"));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2C_LOCAL_MODEL))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (!cmd.arguments().empty())
- {
- QString modelType = cmd.arguments().takeFirst().toUpper();
- if (modelType == "LS")
- {
- modelIndex = 1;
- }
- else if (modelType == "TRI")
- {
- modelIndex = 2;
- }
- else if (modelType == "HF")
- {
- modelIndex = 3;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: unknown model type \"%1\"").arg(modelType));
- }
- }
- else
- {
- return cmd.error(QObject::tr("Missing parameter: model type after \"-%1\" (LS/TRI/HF)").arg(COMMAND_C2C_LOCAL_MODEL));
- }
-
- if (!cmd.arguments().empty())
- {
- QString nType = cmd.arguments().takeFirst().toUpper();
- if (nType == "KNN")
- {
- useKNN = true;
- }
- else if (nType == "SPHERE")
- {
- useKNN = false;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: unknown neighborhood type \"%1\"").arg(nType));
- }
- }
- else
- {
- return cmd.error(QObject::tr("Missing parameter: expected neighborhood type after model type (KNN/SPHERE)"));
- }
-
- //neighborhood size
- if (!cmd.arguments().empty())
- {
- bool conversionOk = false;
- nSize = cmd.arguments().takeFirst().toDouble(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: neighborhood size"));
- }
- }
- else
- {
- return cmd.error(QObject::tr("Missing parameter: expected neighborhood size after neighborhood type (neighbor count/sphere radius)"));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_MAX_THREAD_COUNT))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: max thread count after '%1'").arg(COMMAND_MAX_THREAD_COUNT));
- }
-
- bool ok;
- maxThreadCount = cmd.arguments().takeFirst().toInt(&ok);
- if (!ok || maxThreadCount < 0)
- {
- return cmd.error(QObject::tr("Invalid thread count! (after %1)").arg(COMMAND_MAX_THREAD_COUNT));
- }
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
-
- //spawn dialog (virtually) so as to prepare the comparison process
- ccComparisonDlg compDlg(compCloud,
- refEntity,
- m_cloud2meshDist ? ccComparisonDlg::CLOUDMESH_DIST : ccComparisonDlg::CLOUDCLOUD_DIST,
- cmd.widgetParent(),
- true);
- if (!compDlg.initDialog())
- {
- return cmd.error(QObject::tr("Failed to initialize comparison dialog"));
- }
-
- //update parameters
- if (maxDist > 0)
- {
- compDlg.maxDistCheckBox->setChecked(true);
- compDlg.maxSearchDistSpinBox->setValue(maxDist);
- }
- if (octreeLevel > 0)
- {
- compDlg.octreeLevelComboBox->setCurrentIndex(octreeLevel);
- }
- if (maxThreadCount != 0)
- {
- compDlg.maxThreadCountSpinBox->setValue(maxThreadCount);
- }
-
- if (m_cloud2meshDist)
- {
- //C2M-only parameters
- compDlg.flipNormalsCheckBox->setChecked(flipNormals);
- compDlg.signedDistCheckBox->setChecked(!unsignedDistances);
- compDlg.robustCheckBox->setChecked(robust);
- }
- else
- {
- //C2C-only parameters
- if (splitXYZ)
- {
- //DGM: not true anymore
- //if (maxDist > 0)
- // cmd.warning("'Split XYZ' option is ignored if max distance is defined!");
- compDlg.split3DCheckBox->setChecked(true);
- }
- if (mergeXY)
- compDlg.compute2DCheckBox->setChecked(true);
- if (modelIndex != 0)
- {
- compDlg.localModelComboBox->setCurrentIndex(modelIndex);
- if (useKNN)
- {
- compDlg.lmKNNRadioButton->setChecked(true);
- compDlg.lmKNNSpinBox->setValue(static_cast<int>(nSize));
- }
- else
- {
- compDlg.lmRadiusRadioButton->setChecked(true);
- compDlg.lmRadiusDoubleSpinBox->setValue(nSize);
- }
- }
- }
-
- if (!compDlg.computeDistances())
- {
- compDlg.cancelAndExit();
- return cmd.error(QObject::tr("An error occurred during distances computation!"));
- }
-
- compDlg.applyAndExit();
-
- QString suffix(m_cloud2meshDist ? "_C2M_DIST" : "_C2C_DIST");
- if (maxDist > 0)
- {
- suffix += QObject::tr("_MAX_DIST_%1").arg(maxDist);
- }
-
- compEntity->basename += suffix;
-
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(*compEntity);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
-
- return true;
- }
- CommandC2MDist::CommandC2MDist()
- : CommandDist(true, QObject::tr("C2M distance"), COMMAND_C2M_DIST)
- {}
- CommandC2CDist::CommandC2CDist()
- : CommandDist(false, QObject::tr("C2C distance"), COMMAND_C2C_DIST)
- {}
- CommandCPS::CommandCPS()
- : ccCommandLineInterface::Command(QObject::tr("Closest Point Set"), COMMAND_CLOSEST_POINT_SET)
- {}
- bool CommandCPS::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().size() < 2)
- {
- return cmd.error(QObject::tr("At least two point clouds are needed to compute the closest point set!"));
- }
- else if (cmd.clouds().size() > 2)
- {
- cmd.warning(QObject::tr("More than 3 point clouds loaded! We take the second one as reference by default"));
- }
- // COMPARED CLOUD / REFERENCE CLOUD
- CLCloudDesc compDesc = cmd.clouds().front();
- CLCloudDesc refDesc = cmd.clouds()[1];
- ccPointCloud* compPointCloud = compDesc.pc;
- ccPointCloud* refPointCloud = refDesc.pc;
- assert(compPointCloud && refPointCloud);
- ccProgressDialog pDlg(true, nullptr);
- CCCoreLib::DistanceComputationTools::Cloud2CloudDistancesComputationParams params;
- CCCoreLib::ReferenceCloud closestPointSet(refPointCloud);
- params.CPSet = &closestPointSet;
- // COMPUTE CLOUD 2 CLOUD DISTANCE, THIS INCLUDES THE CLOSEST POINT SET GENERATION
- int result = CCCoreLib::DistanceComputationTools::computeCloud2CloudDistances(compPointCloud, refPointCloud, params, &pDlg);
- if (result >= 0)
- {
- // the extracted CPS will get the attributes of the reference cloud
- ccPointCloud* newCloud = refPointCloud->partialClone(&closestPointSet);
- // give to newCloud a name similar to the one generated by the GUI Closest Point Set tool
- CLCloudDesc desc(
- newCloud,
- "[" + refDesc.basename + "]_CPSet(" + compDesc.basename + ")",
- cmd.clouds()[0].path);
- QString errorStr = cmd.exportEntity(desc, QString(), nullptr, ccCommandLineInterface::ExportOption::ForceNoTimestamp);
- if (!errorStr.isEmpty())
- {
- cmd.error(errorStr);
- }
- //add cloud to the current pool
- cmd.clouds().push_back(desc);
- }
- return true;
- }
- CommandStatTest::CommandStatTest()
- : ccCommandLineInterface::Command(QObject::tr("Statistical test"), COMMAND_STAT_TEST)
- {}
- bool CommandStatTest::process(ccCommandLineInterface& cmd)
- {
- //distribution
- CCCoreLib::GenericDistribution* distrib = nullptr;
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: distribution type after \"-%1\" (GAUSS/WEIBULL)").arg(COMMAND_STAT_TEST));
- }
-
- QString distribStr = cmd.arguments().takeFirst().toUpper();
- if (distribStr == "GAUSS")
- {
- //mu
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: mean value after \"GAUSS\""));
- }
- bool conversionOk = false;
- double mu = cmd.arguments().takeFirst().toDouble(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: mean value after \"GAUSS\""));
- }
- //sigma
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: sigma value after \"GAUSS\" {mu}"));
- }
- conversionOk = false;
- double sigma = cmd.arguments().takeFirst().toDouble(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: sigma value after \"GAUSS\" {mu}"));
- }
-
- CCCoreLib::NormalDistribution* N = new CCCoreLib::NormalDistribution();
- N->setParameters(static_cast<ScalarType>(mu), static_cast<ScalarType>(sigma*sigma)); //warning: we input sigma2 here (not sigma)
- distrib = static_cast<CCCoreLib::GenericDistribution*>(N);
- }
- else if (distribStr == "WEIBULL")
- {
- //a
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: a value after \"WEIBULL\""));
- }
- bool conversionOk = false;
- double a = cmd.arguments().takeFirst().toDouble(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: a value after \"WEIBULL\""));
- }
- //b
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: b value after \"WEIBULL\" {a}"));
- }
- conversionOk = false;
- double b = cmd.arguments().takeFirst().toDouble(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: b value after \"WEIBULL\" {a}"));
- }
- //c
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: shift value after \"WEIBULL\" {a} {b}"));
- }
- conversionOk = false;
- double shift = cmd.arguments().takeFirst().toDouble(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: shift value after \"WEIBULL\" {a} {b}"));
- }
-
- CCCoreLib::WeibullDistribution* N = new CCCoreLib::WeibullDistribution();
- N->setParameters(static_cast<ScalarType>(a), static_cast<ScalarType>(b), static_cast<ScalarType>(shift));
- distrib = static_cast<CCCoreLib::GenericDistribution*>(N);
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: unknown distribution \"%1\"").arg(distribStr));
- }
- }
-
- //pValue
- double pValue = 0.0005;
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: p-value after distribution"));
- }
- bool conversionOk = false;
- pValue = cmd.arguments().takeFirst().toDouble(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: p-value after distribution"));
- }
- }
-
- //kNN
- unsigned kNN = 16;
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: neighbors after p-value"));
- }
- bool conversionOk = false;
- kNN = cmd.arguments().takeFirst().toUInt(&conversionOk);
- if (!conversionOk)
- {
- return cmd.error(QObject::tr("Invalid parameter: neighbors after p-value"));
- }
- }
-
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No cloud available. Be sure to open one first!"));
- }
-
- QScopedPointer<ccProgressDialog> progressDialog(nullptr);
- if (!cmd.silentMode())
- {
- progressDialog.reset(new ccProgressDialog(false, cmd.widgetParent()));
- progressDialog->setAutoClose(false);
- }
-
- for (CLCloudDesc& desc : cmd.clouds())
- {
- //we apply method on currently 'output' SF
- CCCoreLib::ScalarField* outSF = desc.pc->getCurrentOutScalarField();
- if (outSF)
- {
- assert(outSF->capacity() != 0);
-
- //force Chi2 Distances field as 'IN' field (create it by the way if necessary)
- int chi2SfIdx = desc.pc->getScalarFieldIndexByName(CC_CHI2_DISTANCES_DEFAULT_SF_NAME);
- if (chi2SfIdx < 0)
- {
- chi2SfIdx = desc.pc->addScalarField(CC_CHI2_DISTANCES_DEFAULT_SF_NAME);
- }
- if (chi2SfIdx < 0)
- {
- delete distrib;
- return cmd.error(QObject::tr("Couldn't allocate a new scalar field for computing chi2 distances! Try to free some memory ..."));
- }
- desc.pc->setCurrentInScalarField(chi2SfIdx);
-
- //compute octree if necessary
- ccOctree::Shared theOctree = desc.pc->getOctree();
- if (!theOctree)
- {
- theOctree = desc.pc->computeOctree(progressDialog.data());
- if (!theOctree)
- {
- delete distrib;
- cmd.error(QObject::tr("Couldn't compute octree for cloud '%1'!").arg(desc.pc->getName()));
- break;
- }
- }
-
- double chi2dist = CCCoreLib::StatisticalTestingTools::testCloudWithStatisticalModel(distrib, desc.pc, kNN, pValue, progressDialog.data(), theOctree.data());
-
- cmd.print(QObject::tr("[Chi2 Test] %1 test result = %2").arg(distrib->getName()).arg(chi2dist));
-
- //we set the theoretical Chi2 distance limit as the minimum displayed SF value so that all points below are grayed
- {
- ccScalarField* chi2SF = static_cast<ccScalarField*>(desc.pc->getCurrentInScalarField());
- assert(chi2SF);
- chi2SF->computeMinAndMax();
- chi2dist *= chi2dist;
- chi2SF->setMinDisplayed(static_cast<ScalarType>(chi2dist));
- chi2SF->setSymmetricalScale(false);
- chi2SF->setSaturationStart(static_cast<ScalarType>(chi2dist));
- //chi2SF->setSaturationStop(chi2dist);
- desc.pc->setCurrentDisplayedScalarField(chi2SfIdx);
- desc.pc->showSF(true);
- }
-
- desc.basename += QObject::tr("_STAT_TEST_%1").arg(distrib->getName());
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
-
- if (progressDialog)
- {
- progressDialog->close();
- QCoreApplication::processEvents();
- }
-
- return true;
- }
- CommandDelaunayTri::CommandDelaunayTri()
- : ccCommandLineInterface::Command(QObject::tr("Delaunay triangulation"), COMMAND_DELAUNAY)
- {}
- bool CommandDelaunayTri::process(ccCommandLineInterface& cmd)
- {
- bool axisAligned = true;
- double maxEdgeLength = 0;
-
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_DELAUNAY_AA))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- axisAligned = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_DELAUNAY_BF))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- axisAligned = false;
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_DELAUNAY_MAX_EDGE_LENGTH))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
-
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: max edge length value after '%1'").arg(COMMAND_DELAUNAY_MAX_EDGE_LENGTH));
- }
- bool ok;
- maxEdgeLength = cmd.arguments().takeFirst().toDouble(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid value for max edge length (%1)! (after %2)").arg(maxEdgeLength).arg(COMMAND_DELAUNAY_MAX_EDGE_LENGTH));
- }
- }
- else
- {
- break;
- }
- }
-
- cmd.print(QObject::tr("Axis aligned: %1").arg(axisAligned ? "yes" : "no"));
-
- //try to triangulate each cloud
- for (CLCloudDesc& desc : cmd.clouds())
- {
- cmd.print(QObject::tr("\tProcessing cloud %1").arg(!desc.pc->getName().isEmpty() ? desc.pc->getName() : "no name"));
-
- ccMesh* mesh = ccMesh::Triangulate(desc.pc,
- axisAligned ? CCCoreLib::DELAUNAY_2D_AXIS_ALIGNED : CCCoreLib::DELAUNAY_2D_BEST_LS_PLANE,
- false,
- static_cast<PointCoordinateType>(maxEdgeLength),
- 2 //XY plane by default
- );
-
- if (mesh)
- {
- cmd.print(QObject::tr("\tResulting mesh: #%1 faces, %2 vertices").arg(mesh->size()).arg(mesh->getAssociatedCloud()->size()));
-
- CLMeshDesc meshDesc;
- {
- meshDesc.mesh = mesh;
- meshDesc.basename = desc.basename;
- meshDesc.path = desc.path;
- meshDesc.indexInFile = desc.indexInFile;
- }
-
- //save mesh
- if (cmd.autoSaveMode())
- {
- QString outputFilename;
- QString errorStr = cmd.exportEntity(meshDesc, "DELAUNAY", &outputFilename);
- if (!errorStr.isEmpty())
- {
- cmd.warning(errorStr);
- }
- }
-
- //add the resulting mesh to the main set
- cmd.meshes().push_back(meshDesc);
-
- //the mesh takes ownership of the cloud.
- //Therefore we have to remove all clouds from the 'cloud set'! (see below)
- //(otherwise bad things will happen when we'll clear it later ;)
- desc.pc->setEnabled(false);
- mesh->addChild(desc.pc);
- }
- }
- //mehses have taken ownership of the clouds!
- cmd.clouds().resize(0);
-
- return true;
- }
- CommandSFArithmetic::CommandSFArithmetic()
- : ccCommandLineInterface::Command(QObject::tr("SF arithmetic"), COMMAND_SF_ARITHMETIC)
- {}
- bool CommandSFArithmetic::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().size() < 2)
- {
- return cmd.error(QObject::tr("Missing parameter(s): SF index and/or operation after '%1' (2 values expected)").arg(COMMAND_SF_ARITHMETIC));
- }
-
- //read SF index
- int sfIndex = -1;
- QString sfName;
- if (!GetSFIndexOrName(cmd, sfIndex, sfName, true))
- {
- return false;
- }
- //read operation type
- ccScalarFieldArithmeticsDlg::Operation operation = ccScalarFieldArithmeticsDlg::INVALID;
- {
- QString opName = cmd.arguments().takeFirst();
- operation = ccScalarFieldArithmeticsDlg::GetOperationByName(opName);
- if (operation == ccScalarFieldArithmeticsDlg::INVALID)
- {
- return cmd.error(QObject::tr("Unknown operation! (%1)").arg(opName));
- }
- else if (operation <= ccScalarFieldArithmeticsDlg::MAX || operation == ccScalarFieldArithmeticsDlg::SET)
- {
- return cmd.error(QObject::tr("Operation %1 can't be applied with %2. Consider using the %3 command").arg(opName, COMMAND_SF_ARITHMETIC, COMMAND_SF_OP));
- }
- }
- bool inPlace = false;
- //read the optional arguments
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_SF_ARITHMETIC_IN_PLACE))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- inPlace = true;
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
-
- //apply operation on clouds
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- int thisSFIndex = GetScalarFieldIndex(desc.pc, sfIndex, sfName, true);
- if (thisSFIndex >= 0)
- {
- if (!ccScalarFieldArithmeticsDlg::Apply(desc.pc, operation, thisSFIndex, inPlace))
- {
- return cmd.error(QObject::tr("Failed to apply operation on cloud '%1'").arg(desc.pc->getName()));
- }
- else if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc, "SF_ARITHMETIC");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
-
- //and meshes!
- for (size_t j = 0; j < cmd.meshes().size(); ++j)
- {
- bool isLocked = false;
- ccGenericMesh* mesh = cmd.meshes()[j].mesh;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(mesh, &isLocked);
- if (cloud && !isLocked)
- {
- int thisSFIndex = GetScalarFieldIndex(cloud, sfIndex, sfName, true);
- if (thisSFIndex >= 0)
- {
- if (!ccScalarFieldArithmeticsDlg::Apply(cloud, operation, thisSFIndex, inPlace))
- {
- return cmd.error(QObject::tr("Failed to apply operation on mesh '%1'").arg(mesh->getName()));
- }
- else if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(cmd.meshes()[j], "SF_ARITHMETIC");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
-
- return true;
- }
- CommandSFOperation::CommandSFOperation()
- : ccCommandLineInterface::Command(QObject::tr("SF operation"), COMMAND_SF_OP)
- {}
- bool CommandSFOperation::process(ccCommandLineInterface& cmd)
- {
- //in place modifier, to keep old commands intact we should keep it in place by default. However it makes the command line inconsistent, because the SF_ARITHMETIC works the other way.
- bool inPlace = true;
- if (!cmd.arguments().empty())
- {
- if (cmd.IsCommand(cmd.arguments().front(), COMMAND_SF_OP_NOT_IN_PLACE))
- {
- //local arg detected
- inPlace = false;
- cmd.arguments().pop_front();
- }
- }
- if (cmd.arguments().size() < 3)
- {
- return cmd.error(QObject::tr("Missing parameter(s): SF index and/or operation and/or scalar value after '%1' (3 values expected)").arg(COMMAND_SF_OP));
- }
- //read SF index
- int sfIndex = -1;
- QString sfName;
- if (!GetSFIndexOrName(cmd, sfIndex, sfName, true))
- {
- return false;
- }
- //read operation type
- ccScalarFieldArithmeticsDlg::Operation operation = ccScalarFieldArithmeticsDlg::INVALID;
- {
- QString opName = cmd.arguments().takeFirst();
- operation = ccScalarFieldArithmeticsDlg::GetOperationByName(opName);
- if (operation == ccScalarFieldArithmeticsDlg::INVALID)
- {
- return cmd.error(QObject::tr("Unknown operation! (%1)").arg(opName));
- }
- else if (operation > ccScalarFieldArithmeticsDlg::MAX && operation != ccScalarFieldArithmeticsDlg::SET)
- {
- return cmd.error(QObject::tr("Operation %1 can't be applied with %2. Consider using the %3 command").arg(opName, COMMAND_SF_OP, COMMAND_SF_ARITHMETIC));
- }
- }
-
- //read scalar value
- ScalarType value = static_cast<ScalarType>(1.0);
- USE_SPECIAL_SF_VALUE specialValue = USE_SPECIAL_SF_VALUE::USE_NONE;
- {
- QString valueStr = cmd.arguments().takeFirst();
- specialValue = ToSpecialSFValue(valueStr);
- if (specialValue == USE_NONE)
- {
- bool ok = false;
- value = valueStr.toDouble(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid scalar value! (after %1)").arg(COMMAND_SF_OP));
- }
- }
- }
-
- ccScalarFieldArithmeticsDlg::SF2 sf2;
- {
- sf2.isConstantValue = true;
- sf2.constantValue = value;
- }
- //apply operation on clouds
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- int thisSFIndex = GetScalarFieldIndex(desc.pc, sfIndex, sfName, true);
- if (thisSFIndex >= 0)
- {
- sf2.constantValue = GetSFValue(*desc.pc, thisSFIndex, value, specialValue);
- if (!ccScalarFieldArithmeticsDlg::Apply(desc.pc, operation, thisSFIndex, inPlace, &sf2))
- {
- return cmd.error(QObject::tr("Failed to apply operation on cloud '%1'").arg(desc.pc->getName()));
- }
- else if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc, "SF_OP");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
-
- //and meshes!
- for (size_t j = 0; j < cmd.meshes().size(); ++j)
- {
- bool isLocked = false;
- ccGenericMesh* mesh = cmd.meshes()[j].mesh;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(mesh, &isLocked);
- if (cloud && !isLocked)
- {
- int thisSFIndex = GetScalarFieldIndex(cloud, sfIndex, sfName, true);
- if (thisSFIndex >= 0)
- {
- sf2.constantValue = GetSFValue(*cloud, thisSFIndex, value, specialValue);
- if (!ccScalarFieldArithmeticsDlg::Apply(cloud, operation, thisSFIndex, inPlace, &sf2))
- {
- return cmd.error(QObject::tr("Failed to apply operation on mesh '%1'").arg(mesh->getName()));
- }
- else if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(cmd.meshes()[j], "SF_OP");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
-
- return true;
- }
- CommandSFOperationSF::CommandSFOperationSF()
- : ccCommandLineInterface::Command(QObject::tr("SF (add, sub, mult, div) SF"), COMMAND_SF_OP_SF)
- {}
- bool CommandSFOperationSF::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().size() < 3)
- {
- return cmd.error(QObject::tr("Missing parameter(s): SF index and operation and SF index '%1' (3 values expected)").arg(COMMAND_SF_OP_SF));
- }
- //read SF index 1
- int sfIndex = -1;
- QString sfName;
- if (!GetSFIndexOrName(cmd, sfIndex, sfName))
- {
- return false;
- }
- //read operation type
- ccScalarFieldArithmeticsDlg::Operation operation = ccScalarFieldArithmeticsDlg::INVALID;
- {
- QString opName = cmd.arguments().takeFirst();
- operation = ccScalarFieldArithmeticsDlg::GetOperationByName(opName);
- if (operation == ccScalarFieldArithmeticsDlg::INVALID)
- {
- return cmd.error(QObject::tr("Unknown operation! (%1)").arg(opName));
- }
- else if (operation > ccScalarFieldArithmeticsDlg::MAX)
- {
- return cmd.error(QObject::tr("Operation %1 can't be applied with %2").arg(opName, COMMAND_SF_OP_SF));
- }
- }
- //read SF index 2
- int sfIndex2 = -1;
- QString sfName2;
- if (!GetSFIndexOrName(cmd, sfIndex2, sfName2))
- {
- return false;
- }
- //apply operation on clouds
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- int thisSFFIndex = GetScalarFieldIndex(desc.pc, sfIndex, sfName);
- int thisSFFIndex2 = GetScalarFieldIndex(desc.pc, sfIndex2, sfName2);
- if (thisSFFIndex >= 0 && thisSFFIndex2 >= 0)
- {
- ccScalarFieldArithmeticsDlg::SF2 sf2;
- {
- sf2.isConstantValue = false;
- sf2.sfIndex = thisSFFIndex2;
- }
- if (!ccScalarFieldArithmeticsDlg::Apply(desc.pc, operation, thisSFFIndex, true, &sf2))
- {
- return cmd.error(QObject::tr("Failed top apply operation on cloud '%1'").arg(desc.pc->getName()));
- }
- else if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc, "SF_OP_SF");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
- //and meshes!
- for (size_t j = 0; j < cmd.meshes().size(); ++j)
- {
- bool isLocked = false;
- ccGenericMesh* mesh = cmd.meshes()[j].mesh;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(mesh, &isLocked);
- if (cloud && !isLocked)
- {
- int thisSFFIndex = GetScalarFieldIndex(cloud, sfIndex, sfName);
- int thisSFFIndex2 = GetScalarFieldIndex(cloud, sfIndex2, sfName2);
- if (thisSFFIndex >= 0 && thisSFFIndex2 >= 0)
- {
- ccScalarFieldArithmeticsDlg::SF2 sf2;
- {
- sf2.isConstantValue = false;
- sf2.sfIndex = thisSFFIndex2;
- }
- if (!ccScalarFieldArithmeticsDlg::Apply(cloud, operation, thisSFFIndex, true, &sf2))
- {
- return cmd.error(QObject::tr("Failed top apply operation on mesh '%1'").arg(mesh->getName()));
- }
- else if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(cmd.meshes()[j], "SF_OP_SF");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
- return true;
- }
- CommandSFInterpolation::CommandSFInterpolation()
- : ccCommandLineInterface::Command(QObject::tr("SF interpolation"), COMMAND_SF_INTERP)
- {}
- bool CommandSFInterpolation::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().size() < 1)
- return cmd.error(QObject::tr("Missing parameter(s): SF index after '%1' (1 value expected)").arg(COMMAND_SF_INTERP));
- if (cmd.clouds().size() < 2)
- return cmd.error(QObject::tr("Unexpected number of clouds for '%1' (at least 2 clouds expected: first = source, second = dest)").arg(COMMAND_SF_INTERP));
- //read sf index or name
- int sfIndex = -1;
- QString sfName;
- if (!GetSFIndexOrName(cmd, sfIndex, sfName, true))
- {
- return false;
- }
- bool destIsFirst = false;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_SF_INTERP_DEST_IS_FIRST))
- {
- cmd.print(QObject::tr("[DEST_IS_FIRST]"));
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- destIsFirst = true;
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
- ccPointCloud* source = cmd.clouds()[0].pc;
- ccPointCloud* dest = cmd.clouds()[1].pc;
- if (destIsFirst) // swap source and destination
- {
- source = cmd.clouds()[1].pc;
- dest = cmd.clouds()[0].pc;
- }
- sfIndex = GetScalarFieldIndex(source, sfIndex, sfName, true);
- if (sfIndex < 0)
- {
- return false;
- }
- cmd.print("SF to interpolate: index " + QString::number(sfIndex) + ", name " + QString::fromStdString(source->getScalarField(sfIndex)->getName()));
- //semi-persistent parameters
- ccPointCloudInterpolator::Parameters params;
- {
- params.method = ccPointCloudInterpolator::Parameters::NEAREST_NEIGHBOR; // nearest neighbor
- params.algo = ccPointCloudInterpolator::Parameters::NORMAL_DIST; // normal distribution
- params.knn = 6;
- params.radius = static_cast<float>(dest->getOwnBB().getDiagNormd() / 100);
- params.sigma = params.radius / 2.5; // see ccInterpolationDlg::onRadiusUpdated
- }
- return ccEntityAction::interpolateSFs(source, dest, sfIndex, params, cmd.widgetParent());
- }
- CommandColorInterpolation::CommandColorInterpolation()
- : ccCommandLineInterface::Command(QObject::tr("Color interpolation"), COMMAND_COLOR_INTERP)
- {}
- bool CommandColorInterpolation::process(ccCommandLineInterface& cmd)
- {
- if (cmd.clouds().size() < 2)
- return cmd.error(QObject::tr("Unexpected number of clouds for '%1' (at least 2 clouds expected: first = source, second = dest)").arg(COMMAND_COLOR_INTERP));
- ccHObject::Container entities;
- entities.push_back(cmd.clouds()[0].pc);
- entities.push_back(cmd.clouds()[1].pc);
- return ccEntityAction::interpolateColors(entities, cmd.widgetParent());
- }
- CommandFilter::CommandFilter()
- : ccCommandLineInterface::Command(QObject::tr("FILTER"), COMMAND_FILTER)
- {}
- bool CommandFilter::process(ccCommandLineInterface& cmd)
- {
- bool applyToRGB = false;
- bool applyToSF = false;
- bool gaussian = false;
- ccPointCloud::RgbFilterOptions(filterParams);
- filterParams.commandLine = true;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, OPTION_SF))
- {
- cmd.arguments().pop_front();
- applyToSF = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_RGB))
- {
- cmd.arguments().pop_front();
- applyToRGB = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_BILATERAL))
- {
- cmd.arguments().pop_front();
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::NONE)
- {
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::BILATERAL;
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_GAUSSIAN))
- {
- cmd.arguments().pop_front();
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::NONE)
- {
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN;
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_MEAN))
- {
- cmd.arguments().pop_front();
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::NONE)
- {
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::MEAN;
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_MEDIAN))
- {
- cmd.arguments().pop_front();
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::NONE)
- {
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::MEDIAN;
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_SIGMA))
- {
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: spatial sigma after '-%1'").arg(OPTION_SIGMA));
- }
- bool ok = false;
- filterParams.spatialSigma = cmd.arguments().takeFirst().toDouble(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid value for spatial sigma after '%1'!").arg(OPTION_SIGMA));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_SIGMA_SF))
- {
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: spatial sigma after '-%1'").arg(OPTION_SIGMA_SF));
- }
- bool ok = false;
- filterParams.sigmaSF = cmd.arguments().takeFirst().toDouble(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid value for spatial sigma after '%1'!").arg(OPTION_SIGMA_SF));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_BURNT_COLOR_THRESHOLD))
- {
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: burnt color threshold after '-%1'").arg(OPTION_BURNT_COLOR_THRESHOLD));
- }
- bool ok = false;
- uint burntOutColorThreshold = cmd.arguments().takeFirst().toUInt(&ok);
- if (!ok || burntOutColorThreshold > 255)
- {
- return cmd.error(QObject::tr("Invalid value for burnt color threshold after '%1', must be an integer between 0 and 255!").arg(OPTION_BURNT_COLOR_THRESHOLD));
- }
- filterParams.burntOutColorThreshold = static_cast<unsigned char>(burntOutColorThreshold);
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_BLEND_GRAYSCALE))
- {
- cmd.arguments().pop_front();
- if (cmd.arguments().size() < 2)
- {
- return cmd.error(QObject::tr("Missing parameter: blend grayscale threshold and grayscale percent after '-%1'").arg(OPTION_BLEND_GRAYSCALE));
- }
- bool ok = false;
- uint blendGrayscale = cmd.arguments().takeFirst().toUInt(&ok);
- if (!ok || blendGrayscale > 255)
- {
- return cmd.error(QObject::tr("Invalid value for blend grayscale threshold after '%1', must be an integer between 0 and 255!").arg(OPTION_BLEND_GRAYSCALE));
- }
- filterParams.blendGrayscale = true;
- filterParams.blendGrayscaleThreshold = static_cast<unsigned char>(blendGrayscale);
- uint grayscalePercent = cmd.arguments().takeFirst().toUInt(&ok);
- if (!ok || grayscalePercent > 100)
- {
- return cmd.error(QObject::tr("Invalid value for grayscale percent after '%1 %2', must be an integer between 0 and 100!").arg(OPTION_BLEND_GRAYSCALE).arg(filterParams.blendGrayscaleThreshold));
- }
- filterParams.blendGrayscalePercent = static_cast<double>(grayscalePercent) / 100;
- }
- else
- {
- break;
- }
- }
- if (!applyToRGB && !applyToSF)
- {
- return cmd.error(QObject::tr("Missing parameter -%1 and/or -%2 need to be set.").arg(OPTION_RGB).arg(OPTION_SF));
- }
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::NONE)
- {
- return cmd.error(QObject::tr("Missing parameter any of '-%1', '-%2', '-%3', '-%4' need to be set.")
- .arg(OPTION_MEAN)
- .arg(OPTION_GAUSSIAN)
- .arg(OPTION_BILATERAL)
- .arg(OPTION_MEDIAN));
- }
- //apply operation on clouds
- ccHObject::Container selectedEntities;
- for (CLCloudDesc& thisCloudDesc : cmd.clouds())
- {
- selectedEntities.push_back(thisCloudDesc.pc);
- }
- if (applyToSF && applyToRGB)
- {
- applyToSF = false;
- filterParams.applyToSFduringRGB = true;
- }
- if (applyToSF)
- {
- return ccEntityAction::sfGaussianFilter(selectedEntities, filterParams, cmd.widgetParent());
- }
- else if (applyToRGB)
- {
- return ccEntityAction::rgbGaussianFilter(selectedEntities, filterParams, cmd.widgetParent());
- }
-
- return true;
- }
- CommandRenameEntities::CommandRenameEntities()
- : ccCommandLineInterface::Command(QObject::tr("Rename entities"), COMMAND_RENAME_ENTITIES)
- {}
- bool CommandRenameEntities::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: Name after \"-%1\"").arg(COMMAND_RENAME_ENTITIES));
- }
- QString newBaseName = cmd.arguments().takeFirst();
- //Validate if the given name contains any breaking characters for NTFS filesystem at least
- QRegExp rx("[^:/\\\\*?\"|<>]*");
- QRegExpValidator v(rx, 0);
- int pos = 0;
- if (!v.validate(newBaseName, pos))
- {
- assert(false);
- return cmd.error("Name cannot contain any of these characters: :/\\*?\"|<>");
- }
- //apply operation on clouds
- int index = 1;
- size_t nrOfEntities = cmd.clouds().size() + cmd.meshes().size();
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- QString currentCloudName = newBaseName;
- if (nrOfEntities > 1)
- {
- currentCloudName += "-" + QString::number(index).rightJustified(3, '0');
- }
- cmd.print("Cloud '" + desc.pc->getName() + "' renamed to '" + currentCloudName + "'");
- //rename the Point Cloud entity
- desc.pc->setName(currentCloudName);
- //rename the Root entity
- desc.basename = currentCloudName;
- ++index;
- }
- }
- //apply operation on meshes
- for (CLMeshDesc& desc : cmd.meshes())
- {
- if (desc.mesh)
- {
- QString currentMeshName = newBaseName;
- if (nrOfEntities > 1)
- {
- currentMeshName += "-" + QString::number(index).rightJustified(3, '0');
- }
- cmd.print("Mesh '" + desc.mesh->getName() + "' renamed to '" + currentMeshName + "'");
- //rename the Mesh entity
- desc.mesh->setName(currentMeshName);
- //rename the Root entity
- desc.basename = currentMeshName;
- ++index;
- }
- }
- return true;
- }
- CommandSFRename::CommandSFRename()
- : ccCommandLineInterface::Command(QObject::tr("Rename SF"), COMMAND_RENAME_SF)
- {}
- bool CommandSFRename::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().size() < 2)
- {
- return cmd.error(QObject::tr("Missing parameter(s): SF index and/or scalar field name after '%1' (2 values expected)").arg(COMMAND_RENAME_SF));
- }
- //read SF index
- int sfIndex = -1;
- QString sfName;
- if (!GetSFIndexOrName(cmd, sfIndex, sfName, true))
- {
- return false;
- }
- //read the SF name
- QString newSFName = cmd.arguments().takeFirst();
- //apply operation on clouds
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- int thisSFIndex = GetScalarFieldIndex(desc.pc, sfIndex, sfName, true);
- if (thisSFIndex >= 0)
- {
- int indexOfSFWithSameName = desc.pc->getScalarFieldIndexByName(newSFName.toStdString());
- if (indexOfSFWithSameName >= 0 && thisSFIndex != indexOfSFWithSameName)
- {
- return cmd.error("A SF with the same name is already defined on cloud " + desc.pc->getName());
- }
- CCCoreLib::ScalarField* sf = desc.pc->getScalarField(thisSFIndex);
- if (!sf)
- {
- assert(false);
- return cmd.error("Internal error: invalid SF index");
- }
- sf->setName(newSFName.toStdString());
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc, "SF_RENAMED");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
- //and meshes!
- for (CLMeshDesc& desc : cmd.meshes())
- {
- bool isLocked = false;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(desc.mesh, &isLocked);
- if (cloud && !isLocked)
- {
- int thisSFIndex = GetScalarFieldIndex(cloud, sfIndex, sfName, true);
- if (thisSFIndex >= 0)
- {
- int indexOfSFWithSameName = cloud->getScalarFieldIndexByName(newSFName.toStdString());
- if (indexOfSFWithSameName >= 0 && thisSFIndex != indexOfSFWithSameName)
- {
- return cmd.error("A SF with the same name is already defined on cloud " + cloud->getName());
- }
- CCCoreLib::ScalarField* sf = cloud->getScalarField(thisSFIndex);
- if (!sf)
- {
- assert(false);
- return cmd.error("Internal error: invalid SF index");
- }
- sf->setName(newSFName.toStdString());
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc, "SF_RENAMED");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- }
- return true;
- }
- CommandSFAddConst::CommandSFAddConst()
- : ccCommandLineInterface::Command(QObject::tr("Add constant SF"), COMMAND_SF_ADD_CONST)
- {}
- bool CommandSFAddConst::process(ccCommandLineInterface& cmd)
- {
- cmd.print(QObject::tr("Note: this operation is only done on clouds"));
- if (cmd.arguments().size() < 2)
- {
- return cmd.error(QObject::tr("Missing parameter(s): SF name and value after '%1' (2 values expected)").arg(COMMAND_SF_ADD_CONST));
- }
- //read the SF name
- QString sfName = cmd.arguments().takeFirst();
- //read constant value
- bool ok = true;
- ScalarType value = static_cast<ScalarType>(cmd.arguments().takeFirst().toDouble(&ok));
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid constant value! (after %1)").arg(COMMAND_SF_ADD_CONST));
- }
- //apply operation on clouds
- for (CLCloudDesc& desc : cmd.clouds())
- {
- if (desc.pc)
- {
- // check that there is no existing scalar field with the same name
- int indexOfSFWithSameName = desc.pc->getScalarFieldIndexByName(sfName.toStdString());
- if (indexOfSFWithSameName >= 0)
- return cmd.error("A SF with the same name is already defined on cloud " + desc.pc->getName());
- // add the new scalar field
- int sfIndex = desc.pc->addScalarField(sfName.toStdString());
- if (sfIndex == -1)
- {
- return cmd.error("Internal error: addScalarField failed");
- }
- CCCoreLib::ScalarField* sf = desc.pc->getScalarField(sfIndex);
- assert(sf);
- for (unsigned index = 0; index < desc.pc->size(); index++)
- {
- sf->setValue(index, value);
- }
-
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(desc, "SF_ADDED");
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- }
- return true;
- }
- CommandSFAddId::CommandSFAddId()
- : ccCommandLineInterface::Command(QObject::tr("Add indexes as SF"), COMMAND_SF_ADD_ID)
- {}
- bool CommandSFAddId::process(ccCommandLineInterface& cmd)
- {
- ccHObject::Container selectedEntities;
- bool addIdAsInt = false;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_SF_ADD_ID_AS_INT))
- {
- cmd.print(QObject::tr("[AS_INT]"));
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- addIdAsInt = true;
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
- for (CLCloudDesc& desc : cmd.clouds())
- {
- selectedEntities.push_back(desc.getEntity());
- }
- return ccEntityAction::sfAddIdField(selectedEntities, addIdAsInt);
- }
- CommandICP::CommandICP()
- : ccCommandLineInterface::Command("ICP", COMMAND_ICP)
- {}
- bool CommandICP::process(ccCommandLineInterface& cmd)
- {
- //look for local options
- bool referenceIsFirst = false;
- bool adjustScale = false;
- bool enableFarthestPointRemoval = false;
- double minErrorDiff = 1.0e-6;
- unsigned iterationCount = 0;
- unsigned randomSamplingLimit = 20000;
- unsigned overlap = 100;
- int modelWeightsSFIndex = -1;
- QString modelWeightsSFIndexName;
- int dataWeightsSFIndex = -1;
- QString dataWeightsSFIndexName;
- int maxThreadCount = 0;
- int transformationFilters = CCCoreLib::RegistrationTools::SKIP_NONE;
- bool useC2MDistances = false;
- bool robustC2MDistances = true;
- CCCoreLib::ICPRegistrationTools::NORMALS_MATCHING normalsMatching = CCCoreLib::ICPRegistrationTools::NO_NORMAL;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_REFERENCE_IS_FIRST))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- referenceIsFirst = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_ADJUST_SCALE))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- adjustScale = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_ENABLE_FARTHEST_REMOVAL))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- enableFarthestPointRemoval = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_MIN_ERROR_DIIF))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: min error difference after '%1'").arg(COMMAND_ICP_MIN_ERROR_DIIF));
- }
- bool ok;
- minErrorDiff = cmd.arguments().takeFirst().toDouble(&ok);
- if (!ok || minErrorDiff <= 0)
- {
- return cmd.error(QObject::tr("Invalid value for min. error difference! (after %1)").arg(COMMAND_ICP_MIN_ERROR_DIIF));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_ITERATION_COUNT))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number of iterations after '%1'").arg(COMMAND_ICP_ITERATION_COUNT));
- }
- bool ok;
- QString arg = cmd.arguments().takeFirst();
- iterationCount = arg.toUInt(&ok);
- if (!ok || iterationCount == 0)
- return cmd.error(QObject::tr("Invalid number of iterations! (%1)").arg(arg));
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_OVERLAP))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: overlap percentage after '%1'").arg(COMMAND_ICP_OVERLAP));
- }
- bool ok;
- QString arg = cmd.arguments().takeFirst();
- overlap = arg.toUInt(&ok);
- if (!ok || overlap < 10 || overlap > 100)
- {
- return cmd.error(QObject::tr("Invalid overlap value! (%1 --> should be between 10 and 100)").arg(arg));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_RANDOM_SAMPLING_LIMIT))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: random sampling limit value after '%1'").arg(COMMAND_ICP_RANDOM_SAMPLING_LIMIT));
- }
- bool ok;
- randomSamplingLimit = cmd.arguments().takeFirst().toUInt(&ok);
- if (!ok || randomSamplingLimit < 3)
- {
- return cmd.error(QObject::tr("Invalid random sampling limit! (after %1)").arg(COMMAND_ICP_RANDOM_SAMPLING_LIMIT));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_USE_MODEL_SF_AS_WEIGHT))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: SF index after '%1'").arg(COMMAND_ICP_USE_MODEL_SF_AS_WEIGHT));
- }
- //read SF index
- if (!GetSFIndexOrName(cmd, modelWeightsSFIndex, modelWeightsSFIndexName, true))
- {
- return false;
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_USE_DATA_SF_AS_WEIGHT))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: SF index after '%1'").arg(COMMAND_ICP_USE_DATA_SF_AS_WEIGHT));
- }
- //read SF index
- if (!GetSFIndexOrName(cmd, dataWeightsSFIndex, dataWeightsSFIndexName, true))
- {
- return false;
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_MAX_THREAD_COUNT))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: max thread count after '%1'").arg(COMMAND_MAX_THREAD_COUNT));
- }
- bool ok;
- maxThreadCount = cmd.arguments().takeFirst().toInt(&ok);
- if (!ok || maxThreadCount < 0)
- {
- return cmd.error(QObject::tr("Invalid thread count! (after %1)").arg(COMMAND_MAX_THREAD_COUNT));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_ROT))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (!cmd.arguments().empty())
- {
- QString rotation = cmd.arguments().takeFirst().toUpper();
- //invalidate all previous rotations in case -ROT used twice
- cmd.print(QObject::tr("[ICP] Reset rotation constraints if any. Only one -%1 argument allowed").arg(COMMAND_ICP_ROT));
- transformationFilters &= (~CCCoreLib::RegistrationTools::SKIP_ROTATION);
- if (rotation == "XYZ")
- {
- cmd.print(QObject::tr("[ICP] Use all rotations"));
- }
- else if (rotation == "X")
- {
- transformationFilters |= CCCoreLib::RegistrationTools::SKIP_RYZ;
- cmd.print(QObject::tr("[ICP] Skip RYZ"));
- }
- else if (rotation == "Y")
- {
- transformationFilters |= CCCoreLib::RegistrationTools::SKIP_RXZ;
- cmd.print(QObject::tr("[ICP] Skip RXZ"));
- }
- else if (rotation == "Z")
- {
- transformationFilters |= CCCoreLib::RegistrationTools::SKIP_RXY;
- cmd.print(QObject::tr("[ICP] Skip RXY"));
- }
- else if (rotation == "NONE")
- {
- transformationFilters |= CCCoreLib::RegistrationTools::SKIP_ROTATION;
- cmd.print(QObject::tr("[ICP] Skip rotation"));
- }
- else
- {
- return cmd.error(QObject::tr("Invalid parameter: unknown rotation filter \"%1\"").arg(rotation));
- }
- }
- else
- {
- return cmd.error(QObject::tr("Missing parameter: rotation filter after \"-%1\" (XYZ/X/Y/Z/NONE)").arg(COMMAND_ICP_ROT));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_SKIP_TX))
- {
- transformationFilters |= CCCoreLib::RegistrationTools::SKIP_TX;
- cmd.print(QObject::tr("[ICP] Skip TX"));
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_SKIP_TY))
- {
- transformationFilters |= CCCoreLib::RegistrationTools::SKIP_TY;
- cmd.print(QObject::tr("[ICP] Skip TY"));
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_SKIP_TZ))
- {
- transformationFilters |= CCCoreLib::RegistrationTools::SKIP_TZ;
- cmd.print(QObject::tr("[ICP] Skip TZ"));
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_ICP_C2M_DIST))
- {
- useC2MDistances = true;
- cmd.print(QObject::tr("[ICP] Use C2M distances"));
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2M_DIST_NON_ROBUST))
- {
- robustC2MDistances = false;
- cmd.warning(QObject::tr("[ICP] Use non-robust C2M distances"));
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- }
- else if (ccCommandLineInterface::IsCommand(argument, COMMAND_C2M_NORMAL_MATCHING))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: normals matching mode after '%1'").arg(COMMAND_C2M_NORMAL_MATCHING));
- }
- QString normalsMatchingOption = cmd.arguments().takeFirst().toUpper();
- if (normalsMatchingOption == "OPPOSITE")
- {
- normalsMatching = CCCoreLib::ICPRegistrationTools::OPPOSITE_NORMALS;
- cmd.print(QObject::tr("[ICP] Use opposite normals matching mode"));
- }
- else if (normalsMatchingOption == "SAME_SIDE")
- {
- normalsMatching = CCCoreLib::ICPRegistrationTools::SAME_SIDE_NORMALS;
- cmd.print(QObject::tr("[ICP] Use same-side normals matching mode"));
- }
- else if (normalsMatchingOption == "DOUBLE_SIDED")
- {
- normalsMatching = CCCoreLib::ICPRegistrationTools::DOUBLE_SIDED_NORMALS;
- cmd.print(QObject::tr("[ICP] Use double-sided normals matching mode"));
- }
- else
- {
- return cmd.error(QObject::tr("Unknown normal matching mode: ") + normalsMatchingOption);
- }
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
- cmd.printDebug(QObject::tr("[ICP] Transfromation filter: %1").arg(transformationFilters));
- //we'll get the first two entities
- CLEntityDesc* dataAndModel[2]{ nullptr, nullptr };
- {
- int index = 0;
- if (!cmd.clouds().empty())
- {
- dataAndModel[index++] = &cmd.clouds()[0];
- if (cmd.clouds().size() > 1)
- {
- dataAndModel[index++] = &cmd.clouds()[1];
- }
- }
- if (index < 2 && !cmd.meshes().empty())
- {
- dataAndModel[index++] = &cmd.meshes()[0];
- if (index < 2 && cmd.meshes().size() > 1)
- {
- dataAndModel[index++] = &cmd.meshes()[1];
- }
- }
- if (index < 2)
- {
- return cmd.error(QObject::tr("Not enough loaded entities (expect at least 2!)"));
- }
- }
- //put them in the right order (data first, model next)
- if (referenceIsFirst)
- {
- std::swap(dataAndModel[0], dataAndModel[1]);
- }
- //check that the weights (scalar fields) exist
- if (dataWeightsSFIndex >= 0 || !dataWeightsSFIndexName.isEmpty())
- {
- ccPointCloud* dataAsCloud = ccHObjectCaster::ToPointCloud(dataAndModel[0]->getEntity());
- if (dataAsCloud)
- {
- dataWeightsSFIndex = GetScalarFieldIndex(dataAsCloud, dataWeightsSFIndex, dataWeightsSFIndexName, true);
- if (dataWeightsSFIndex >= 0)
- {
- cmd.print(QObject::tr("[ICP] SF #%1 (data entity) will be used as weights").arg(dataWeightsSFIndex));
- dataAsCloud->setCurrentDisplayedScalarField(dataWeightsSFIndex);
- }
- }
- }
- if (modelWeightsSFIndex >= 0 || !modelWeightsSFIndexName.isEmpty())
- {
- ccPointCloud* modelAsCloud = ccHObjectCaster::ToPointCloud(dataAndModel[1]->getEntity());
- if (modelAsCloud)
- {
- modelWeightsSFIndex = GetScalarFieldIndex(modelAsCloud, modelWeightsSFIndex, modelWeightsSFIndexName, true);
- if (modelWeightsSFIndex >= 0)
- {
- cmd.print(QObject::tr("[ICP] SF #%1 (model entity) will be used as weights").arg(modelWeightsSFIndex));
- modelAsCloud->setCurrentDisplayedScalarField(modelWeightsSFIndex);
- }
- }
- }
-
- ccGLMatrix transMat;
- double finalError = 0.0;
- double finalScale = 1.0;
- unsigned finalPointCount = 0;
- CCCoreLib::ICPRegistrationTools::Parameters parameters;
- {
- parameters.convType = (iterationCount != 0 ? CCCoreLib::ICPRegistrationTools::MAX_ITER_CONVERGENCE : CCCoreLib::ICPRegistrationTools::MAX_ERROR_CONVERGENCE);
- parameters.minRMSDecrease = minErrorDiff;
- parameters.nbMaxIterations = iterationCount;
- parameters.adjustScale = adjustScale;
- parameters.filterOutFarthestPoints = enableFarthestPointRemoval;
- parameters.samplingLimit = randomSamplingLimit;
- parameters.finalOverlapRatio = overlap / 100.0;
- parameters.transformationFilters = transformationFilters;
- parameters.maxThreadCount = maxThreadCount;
- parameters.useC2MSignedDistances = useC2MDistances;
- parameters.robustC2MSignedDistances = robustC2MDistances;
- parameters.normalsMatching = normalsMatching;
- }
- if (ccRegistrationTools::ICP( dataAndModel[0]->getEntity(),
- dataAndModel[1]->getEntity(),
- transMat,
- finalScale,
- finalError,
- finalPointCount,
- parameters,
- dataWeightsSFIndex >= 0,
- modelWeightsSFIndex >= 0,
- cmd.widgetParent()))
- {
- ccHObject* data = dataAndModel[0]->getEntity();
- data->applyGLTransformation_recursive(&transMat);
- cmd.print(QObject::tr("Entity '%1' has been registered").arg(data->getName()));
- cmd.print(QObject::tr("RMS: %1").arg(finalError));
- cmd.print(QObject::tr("Number of points used for final step: %1").arg(finalPointCount));
-
- //save matrix in a separate text file
- {
- QString txtFilename = QObject::tr("%1/%2_REGISTRATION_MATRIX").arg(dataAndModel[0]->path, dataAndModel[0]->basename);
- if (cmd.addTimestamp())
- {
- QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd_hh'h'mm_ss_zzz");
- txtFilename += QString("_%1").arg(timestamp);
- }
- txtFilename += ".txt";
- QFile txtFile(txtFilename);
- if (txtFile.open(QIODevice::WriteOnly | QIODevice::Text))
- {
- QTextStream txtStream(&txtFile);
- txtStream << transMat.toString(cmd.numericalPrecision(), ' ') << endl;
- txtFile.close();
- }
- else
- {
- cmd.warning("Failed to save the registration matrix to file " + txtFilename);
- }
- }
-
- dataAndModel[0]->basename += QObject::tr("_REGISTERED");
- if (cmd.autoSaveMode())
- {
- QString errorStr = cmd.exportEntity(*dataAndModel[0]);
- if (!errorStr.isEmpty())
- {
- return cmd.error(errorStr);
- }
- }
- }
- else
- {
- return false;
- }
-
- return true;
- }
- CommandChangePLYExportFormat::CommandChangePLYExportFormat()
- : ccCommandLineInterface::Command(QObject::tr("Change PLY output format"), COMMAND_PLY_EXPORT_FORMAT)
- {}
- bool CommandChangePLYExportFormat::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: format (ASCII, BINARY_LE, or BINARY_BE) after '%1'").arg(COMMAND_PLY_EXPORT_FORMAT));
- }
-
- //if (fileFilter != PlyFilter::GetFileFilter())
- // cmd.warning(QObject::tr("Argument '%1' is only applicable to PLY format!").arg(argument));
-
- QString plyFormat = cmd.arguments().takeFirst().toUpper();
- //printf("%s\n",qPrintable(plyFormat));
-
- if (plyFormat == "ASCII")
- {
- PlyFilter::SetDefaultOutputFormat(PLY_ASCII);
- }
- else if (plyFormat == "BINARY_BE")
- {
- PlyFilter::SetDefaultOutputFormat(PLY_BIG_ENDIAN);
- }
- else if (plyFormat == "BINARY_LE")
- {
- PlyFilter::SetDefaultOutputFormat(PLY_LITTLE_ENDIAN);
- }
- else
- {
- return cmd.error(QObject::tr("Invalid PLY format! ('%1')").arg(plyFormat));
- }
-
- return true;
- }
- CommandForceNormalsComputation::CommandForceNormalsComputation()
- : ccCommandLineInterface::Command(QObject::tr("Compute structured cloud normals"), COMMAND_COMPUTE_GRIDDED_NORMALS)
- {}
- bool CommandForceNormalsComputation::process(ccCommandLineInterface& cmd)
- {
- //simply change the default filter behavior
- cmd.fileLoadingParams().autoComputeNormals = true;
-
- return true;
- }
- CommandSave::CommandSave(const QString& name, const QString& keyword)
- : ccCommandLineInterface::Command(name, keyword)
- {}
- bool CommandSave::ParseFileNames(ccCommandLineInterface& cmd, QStringList &fileNames)
- {
- //
- // File list is space separated, but can use quotes to include spaces in the file names
- //
- auto argument = cmd.arguments().takeFirst();
- while (!argument.isEmpty())
- {
- auto firstChar = argument.at(0);
- if (firstChar == '\'' || firstChar == '\"')
- {
- auto end = argument.indexOf(firstChar, 1);
- if (end == -1)
- {
- return cmd.error(QObject::tr("A file starting with %1 does not have a closing %1").arg(firstChar));
- }
-
- fileNames.push_back(argument.mid(1, end - 1));
- argument.remove(0, end + 1);
- if (argument.startsWith(' '))
- {
- argument.remove(0, 1);
- }
- }
- else
- {
- auto end = argument.indexOf(' ');
- if (end == -1)
- {
- end = argument.length();
- }
- fileNames.push_back(argument.left(end));
- argument.remove(0, end + 1);
- }
- }
- return true;
- }
- void CommandSave::SetFileDesc(CLEntityDesc &desc, const QString &fileName)
- {
- QFileInfo fInfo(fileName);
- desc.basename = fInfo.fileName();
- desc.path = fInfo.filePath().left(fInfo.filePath().length() - fInfo.fileName().length());
- }
- CommandSaveClouds::CommandSaveClouds()
- : CommandSave(QObject::tr("Save clouds"), COMMAND_SAVE_CLOUDS)
- {}
- bool CommandSaveClouds::process(ccCommandLineInterface& cmd)
- {
- bool allAtOnce = false;
- bool setFileNames = false;
- QStringList fileNames;
-
- //look for additional parameters
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (argument.toUpper() == OPTION_ALL_AT_ONCE)
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- allAtOnce = true;
- }
- else if (argument.startsWith(OPTION_FILE_NAMES))
- {
- cmd.arguments().pop_front();
- setFileNames = true;
- if (!ParseFileNames(cmd, fileNames))
- {
- return false;
- }
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
-
- if (setFileNames && allAtOnce && fileNames.size() != 1)
- {
- return cmd.error(QObject::tr("Invalid parameter: specified %1 file names, but ALL_AT_ONCE is on").arg(fileNames.size()));
- }
- if (setFileNames && !allAtOnce && fileNames.size() != cmd.clouds().size())
- {
- return cmd.error(QObject::tr("Invalid parameter: specified %1 file names, but there are %2 clouds").arg(fileNames.size()).arg(cmd.clouds().size()));
- }
-
- QString ext = cmd.cloudExportExt();
- bool autoAddTimestamp = cmd.addTimestamp();
- if (setFileNames)
- {
- cmd.toggleAddTimestamp(false);
- cmd.setCloudExportFormat(cmd.cloudExportFormat(), QString());
-
- if (!allAtOnce)
- {
- for (int i = 0; i < fileNames.size(); ++i)
- {
- SetFileDesc(cmd.clouds()[i], fileNames[i]);
- }
- }
- }
-
- bool res = cmd.saveClouds(QString(), allAtOnce, allAtOnce && setFileNames ? &fileNames[0] : nullptr);
-
- if (setFileNames)
- {
- cmd.toggleAddTimestamp(autoAddTimestamp);
- cmd.setCloudExportFormat(cmd.cloudExportFormat(), ext);
- }
-
- return res;
- }
- CommandSaveMeshes::CommandSaveMeshes()
- : CommandSave(QObject::tr("Save meshes"), COMMAND_SAVE_MESHES)
- {}
- bool CommandSaveMeshes::process(ccCommandLineInterface& cmd)
- {
- bool allAtOnce = false;
- bool setFileNames = false;
- QStringList fileNames;
-
- //look for additional parameters
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front();
- if (argument.toUpper() == OPTION_ALL_AT_ONCE)
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- allAtOnce = true;
- }
- else if (argument.left(sizeof(OPTION_FILE_NAMES) - 1).toUpper() == OPTION_FILE_NAMES)
- {
- cmd.arguments().pop_front();
- setFileNames = true;
- if (!ParseFileNames(cmd, fileNames))
- {
- return false;
- }
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
-
- if (setFileNames && allAtOnce && fileNames.size() != 1)
- {
- return cmd.error(QObject::tr("Invalid parameter: specified %1 file names, but ALL_AT_ONCE is on").arg(fileNames.size()));
- }
- if (setFileNames && !allAtOnce && fileNames.size() != cmd.meshes().size())
- {
- return cmd.error(QObject::tr("Invalid parameter: specified %1 file names, but there are %2 meshes").arg(fileNames.size()).arg(cmd.meshes().size()));
- }
-
- QString ext = cmd.meshExportExt();
- bool autoAddTimestamp = cmd.addTimestamp();
- if (setFileNames)
- {
- cmd.toggleAddTimestamp(false);
- cmd.setMeshExportFormat(cmd.meshExportFormat(), QString());
-
- if (!allAtOnce)
- {
- for (int i = 0; i < fileNames.size(); ++i)
- {
- SetFileDesc(cmd.meshes()[i], fileNames[i]);
- }
- }
- }
-
- bool res = cmd.saveMeshes(QString(), allAtOnce, allAtOnce && setFileNames ? &fileNames[0] : nullptr);
-
- if (setFileNames)
- {
- cmd.toggleAddTimestamp(autoAddTimestamp);
- cmd.setMeshExportFormat(cmd.meshExportFormat(), ext);
- }
-
- return res;
- }
- CommandAutoSave::CommandAutoSave()
- : ccCommandLineInterface::Command(QObject::tr("Auto save state"), COMMAND_AUTO_SAVE)
- {}
- bool CommandAutoSave::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: option after '%1' (%2/%3)").arg(COMMAND_AUTO_SAVE, OPTION_ON, OPTION_OFF));
- }
-
- QString option = cmd.arguments().takeFirst().toUpper();
- if (option == OPTION_ON)
- {
- cmd.print(QObject::tr("Auto-save is enabled"));
- cmd.toggleAutoSaveMode(true);
- }
- else if (option == OPTION_OFF)
- {
- cmd.print(QObject::tr("Auto-save is disabled"));
- cmd.toggleAutoSaveMode(false);
- }
- else
- {
- return cmd.error(QObject::tr("Unrecognized option after '%1' (%2 or %3 expected)").arg(COMMAND_AUTO_SAVE, OPTION_ON, OPTION_OFF));
- }
-
- return true;
- }
- CommandLogFile::CommandLogFile()
- : ccCommandLineInterface::Command(QObject::tr("Set log file"), COMMAND_LOG_FILE)
- {}
- bool CommandLogFile::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: filename after '%1'").arg(COMMAND_LOG_FILE));
- }
-
- QString filename = cmd.arguments().takeFirst();
- if (!ccConsole::TheInstance(false))
- {
- assert(cmd.silentMode());
- ccConsole::Init();
- }
-
- return ccConsole::TheInstance()->setLogFile(filename);
- }
- CommandSelectEntities::CommandSelectEntities()
- : ccCommandLineInterface::Command(QObject::tr("Select entities"), COMMAND_SELECT_ENTITIES)
- {}
- bool CommandSelectEntities::process(ccCommandLineInterface& cmd)
- {
- //option handling
- //look for additional parameters
- ccCommandLineInterface::SelectEntitiesOptions options;
- bool selectMeshes = false;
- bool selectClouds = false;
- while (!cmd.arguments().empty())
- {
- QString argument = cmd.arguments().front().toUpper();
- if (ccCommandLineInterface::IsCommand(argument, OPTION_ALL))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- options.selectAll = true;
- //no other params needed
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_FIRST))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- //it requires a number after the argument
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number of entities after %1").arg(OPTION_FIRST));
- }
- bool ok;
- options.firstNr = cmd.arguments().takeFirst().toUInt(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid number after -%1").arg(OPTION_FIRST));
- }
- options.selectFirst = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_LAST))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- //it requires a number after the argument
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: number of entities after %1").arg(OPTION_LAST));
- }
- bool ok;
- options.lastNr = cmd.arguments().takeFirst().toUInt(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid number after -%1").arg(OPTION_LAST));
- }
- options.selectLast = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_REGEX))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- options.selectRegex = true;
- //it requires a string after the argument
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: regex string after %1").arg(OPTION_REGEX));
- }
- QString regexString = cmd.arguments().takeFirst();
- options.regex.setPattern(regexString);
- if (!options.regex.isValid())
- {
- return cmd.error(QObject::tr("Invalid regex pattern: %1").arg(options.regex.errorString()));
- }
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_NOT))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- options.reverse = true;
- //no other params needed
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_CLOUD))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- selectClouds = true;
- }
- else if (ccCommandLineInterface::IsCommand(argument, OPTION_MESH))
- {
- //local option confirmed, we can move on
- cmd.arguments().pop_front();
- selectMeshes = true;
- }
- else
- {
- break; //as soon as we encounter an unrecognized argument, we break the local loop to go back to the main one!
- }
- }
- if (options.selectAll)
- {
- //overwrite any other mode (first/last/regex)
- options.selectFirst = false;
- options.selectLast = false;
- options.selectRegex = false;
- if (options.reverse)
- {
- //NONE
- cmd.print("No entities will be selected.");
- }
- else
- {
- //ALL
- cmd.print("All entities will be selected. Other options will be ignored except -NOT.");
- }
- }
- if (options.selectFirst)
- {
- if (options.reverse)
- {
- if (options.selectLast)
- {
- //not first {nr} and not last {nr}
- cmd.print(QObject::tr("First %1 and last %2 entity(ies) will not be selected").arg(options.firstNr).arg(options.lastNr));
- }
- else
- {
- //not first {nr}
- cmd.print(QObject::tr("First %1 entity(ies) will not be selected").arg(options.firstNr));
- }
- }
- else
- {
- //first {nr}
- cmd.print(QObject::tr("First %1 entity(ies) will be selected").arg(options.firstNr));
- }
- }
- if (options.selectLast)
- {
- if (options.reverse)
- {
- if (!options.selectFirst)
- {
- //not last {nr}
- cmd.print(QObject::tr("Last %1 entity(ies) will not be selected").arg(options.lastNr));
- }
- }
- else
- {
- //last {nr}
- cmd.print(QObject::tr("Last %1 entity(ies) will be selected").arg(options.lastNr));
- }
- }
- if (options.selectRegex)
- {
- if (options.reverse)
- {
- //regex not matches
- cmd.print(QObject::tr("Entities with name matches the regex /%1/ will not be selected.").arg(options.regex.pattern()));
- }
- else
- {
- //regex matches
- cmd.print(QObject::tr("Entities with name matches the regex /%1/ will be selected.").arg(options.regex.pattern()));
- }
- }
- //no option was set
- if (!options.selectFirst && !options.selectLast && !options.selectRegex && !options.selectAll)
- {
- return cmd.error(QObject::tr("Missing parameter(s): any of the option (%1,%2,%3,%4) expected after %5")
- .arg(OPTION_ALL)
- .arg(OPTION_FIRST)
- .arg(OPTION_LAST)
- .arg(OPTION_REGEX)
- .arg(COMMAND_SELECT_ENTITIES));
- }
- //no entity type was selected so select both clouds and meshes
- if (!selectClouds && !selectMeshes)
- {
- selectClouds = true;
- selectMeshes = true;
- }
- if (selectClouds)
- {
- cmd.print(QObject::tr("[Select clouds]"));
- if (!cmd.selectClouds(options))
- {
- //error message already sent
- return false;
- }
- }
- if (selectMeshes)
- {
- cmd.print(QObject::tr("[Select meshes]"));
- if (!cmd.selectMeshes(options))
- {
- //error message already sent
- return false;
- }
- }
- return true;
- }
- CommandClear::CommandClear()
- : ccCommandLineInterface::Command(QObject::tr("Clear"), COMMAND_CLEAR)
- {}
- bool CommandClear::process(ccCommandLineInterface& cmd)
- {
- cmd.removeClouds(false);
- cmd.removeMeshes(false);
- return true;
- }
- CommandClearClouds::CommandClearClouds()
- : ccCommandLineInterface::Command(QObject::tr("Clear clouds"), COMMAND_CLEAR_CLOUDS)
- {}
- bool CommandClearClouds::process(ccCommandLineInterface& cmd)
- {
- cmd.removeClouds(false);
- return true;
- }
- CommandPopClouds::CommandPopClouds()
- : ccCommandLineInterface::Command(QObject::tr("Pop cloud"), COMMAND_POP_CLOUDS)
- {}
- bool CommandPopClouds::process(ccCommandLineInterface& cmd)
- {
- cmd.removeClouds(true);
- return true;
- }
- CommandClearMeshes::CommandClearMeshes()
- : ccCommandLineInterface::Command(QObject::tr("Clear meshes"), COMMAND_CLEAR_MESHES)
- {}
- bool CommandClearMeshes::process(ccCommandLineInterface& cmd)
- {
- cmd.removeMeshes(false);
- return true;
- }
- CommandPopMeshes::CommandPopMeshes()
- : ccCommandLineInterface::Command(QObject::tr("Pop mesh"), COMMAND_POP_MESHES)
- {}
- bool CommandPopMeshes::process(ccCommandLineInterface& cmd)
- {
- cmd.removeMeshes(true);
- return true;
- }
- CommandSetNoTimestamp::CommandSetNoTimestamp()
- : ccCommandLineInterface::Command(QObject::tr("No timestamp"), COMMAND_NO_TIMESTAMP)
- {}
- bool CommandSetNoTimestamp::process(ccCommandLineInterface& cmd)
- {
- cmd.toggleAddTimestamp(false);
- return true;
- }
- CommandMoment::CommandMoment()
- : ccCommandLineInterface::Command(QObject::tr("1st order moment"), COMMAND_MOMENT)
- {}
- bool CommandMoment::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: kernel size after %1").arg(COMMAND_MOMENT));
- }
- bool paramOk = false;
- QString kernelStr = cmd.arguments().takeFirst();
- PointCoordinateType kernelSize = static_cast<PointCoordinateType>(kernelStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: kernel size. Got '%1' instead.").arg(kernelStr));
- }
- cmd.print(QObject::tr("\tKernel size: %1").arg(kernelSize));
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud on which to compute first order moment! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_MOMENT));
- }
- //Call MainWindow generic method
- ccHObject::Container entities;
- entities.resize(cmd.clouds().size());
- for (size_t i = 0; i < cmd.clouds().size(); ++i)
- {
- entities[i] = cmd.clouds()[i].pc;
- }
- if (ccLibAlgorithms::ComputeGeomCharacteristic(CCCoreLib::GeometricalAnalysisTools::MomentOrder1, 0, kernelSize, entities, nullptr, cmd.widgetParent()))
- {
- //save output
- if (cmd.autoSaveMode() && !cmd.saveClouds(QObject::tr("MOMENT_KERNEL_%2").arg(kernelSize)))
- {
- return false;
- }
- }
- return true;
- }
- CommandFeature::CommandFeature()
- : ccCommandLineInterface::Command(QObject::tr("Feature"), COMMAND_FEATURE)
- {}
- bool CommandFeature::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: feature type after \"-%1\"").arg(COMMAND_FEATURE));
- }
- QString featureTypeStr = cmd.arguments().takeFirst().toUpper();
- CCCoreLib::Neighbourhood::GeomFeature featureType;
- if (featureTypeStr == "SUM_OF_EIGENVALUES")
- {
- featureType = CCCoreLib::Neighbourhood::EigenValuesSum;
- }
- else if (featureTypeStr == "OMNIVARIANCE")
- {
- featureType = CCCoreLib::Neighbourhood::Omnivariance;
- }
- else if (featureTypeStr == "EIGENTROPY")
- {
- featureType = CCCoreLib::Neighbourhood::EigenEntropy;
- }
- else if (featureTypeStr == "ANISOTROPY")
- {
- featureType = CCCoreLib::Neighbourhood::Anisotropy;
- }
- else if (featureTypeStr == "PLANARITY")
- {
- featureType = CCCoreLib::Neighbourhood::Planarity;
- }
- else if (featureTypeStr == "LINEARITY")
- {
- featureType = CCCoreLib::Neighbourhood::Linearity;
- }
- else if (featureTypeStr == "PCA1")
- {
- featureType = CCCoreLib::Neighbourhood::PCA1;
- }
- else if (featureTypeStr == "PCA2")
- {
- featureType = CCCoreLib::Neighbourhood::PCA2;
- }
- else if (featureTypeStr == "SURFACE_VARIATION")
- {
- featureType = CCCoreLib::Neighbourhood::SurfaceVariation;
- }
- else if (featureTypeStr == "SPHERICITY")
- {
- featureType = CCCoreLib::Neighbourhood::Sphericity;
- }
- else if (featureTypeStr == "VERTICALITY")
- {
- featureType = CCCoreLib::Neighbourhood::Verticality;
- }
- else if (featureTypeStr == "EIGENVALUE1")
- {
- featureType = CCCoreLib::Neighbourhood::EigenValue1;
- }
- else if (featureTypeStr == "EIGENVALUE2")
- {
- featureType = CCCoreLib::Neighbourhood::EigenValue2;
- }
- else if (featureTypeStr == "EIGENVALUE3")
- {
- featureType = CCCoreLib::Neighbourhood::EigenValue3;
- }
- else
- {
- return cmd.error(QObject::tr("Invalid feature type after \"-%1\". Got '%2' instead of:\n\
- - SUM_OF_EIGENVALUES\n\
- - OMNIVARIANCE\n\
- - EIGENTROPY\n\
- - ANISOTROPY\n\
- - PLANARITY\n\
- - LINEARITY\n\
- - PCA1\n\
- - PCA2\n\
- - SURFACE_VARIATION\n\
- - SPHERICITY\n\
- - VERTICALITY\n\
- - EIGENVALUE1\n\
- - EIGENVALUE2\n\
- - EIGENVALUE3").arg(COMMAND_FEATURE, featureTypeStr));
- }
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: kernel size after feature type"));
- }
- bool paramOk = false;
- QString kernelStr = cmd.arguments().takeFirst();
- PointCoordinateType kernelSize = static_cast<PointCoordinateType>(kernelStr.toDouble(¶mOk));
- if (!paramOk)
- {
- return cmd.error(QObject::tr("Failed to read a numerical parameter: kernel size. Got '%1' instead.").arg(kernelStr));
- }
- cmd.print(QObject::tr("\tKernel size: %1").arg(kernelSize));
- if (cmd.clouds().empty())
- {
- return cmd.error(QObject::tr("No point cloud on which to compute feature! (be sure to open one with \"-%1 [cloud filename]\" before \"-%2\")").arg(COMMAND_OPEN, COMMAND_FEATURE));
- }
- //Call MainWindow generic method on all available clouds
- ccHObject::Container entities;
- {
- entities.resize(cmd.clouds().size());
- for (size_t i = 0; i < cmd.clouds().size(); ++i)
- {
- entities[i] = cmd.clouds()[i].pc;
- }
- }
- if (!ccLibAlgorithms::ComputeGeomCharacteristic(CCCoreLib::GeometricalAnalysisTools::Feature, featureType, kernelSize, entities, nullptr, cmd.widgetParent()))
- {
- return cmd.error(QObject::tr("The computation of some geometric features failed."));
- }
- // on success, update the cloud names
- QString fileNameExt = QObject::tr("%1_FEATURE_KERNEL_%2").arg(featureTypeStr).arg(kernelSize);
- for (size_t i = 0; i < cmd.clouds().size(); ++i)
- {
- CLCloudDesc& desc = cmd.clouds()[i];
- desc.basename += "_" + fileNameExt;
- desc.pc->setName(entities[i]->getName() + QObject::tr(".%1_feature(%2)").arg(featureTypeStr.toLower()).arg(kernelSize));
- }
- //save output
- if (cmd.autoSaveMode() && !cmd.saveClouds())
- {
- return false;
- }
- return true;
- }
- CommandDebugCmdLine::CommandDebugCmdLine()
- : ccCommandLineInterface::Command(QObject::tr("Debug Command Line"), COMMAND_DEBUG)
- {}
- bool CommandDebugCmdLine::process(ccCommandLineInterface& cmd)
- {
- cmd.print("******************************************");
- cmd.print("Number of selected clouds: " + QString::number(cmd.clouds().size()));
- cmd.print("Number of selected meshes: " + QString::number(cmd.meshes().size()));
- cmd.print("******************************************");
- const QStringList& arguments = cmd.arguments();
- cmd.print("Number of arguments: " + QString::number(arguments.size()));
- for (int i = 0; i < arguments.size(); ++i)
- {
- cmd.print(QString("Argument #%1: < %2 >").arg(i + 1).arg(arguments[i]));
- }
- cmd.print("******************************************");
- cmd.print("[Loading parameters]");
- cmd.print(QObject::tr("Global shift set: ") + (cmd.fileLoadingParams().coordinatesShiftEnabled ? "yes" : "no"));
- cmd.print(QObject::tr("Global shift: (%1, %2, %3)").arg(cmd.fileLoadingParams().coordinatesShift.x).arg(cmd.fileLoadingParams().coordinatesShift.y).arg(cmd.fileLoadingParams().coordinatesShift.z));
- cmd.print("******************************************");
- cmd.print("[Export parameters]");
- cmd.print("Cloud export format: " + cmd.cloudExportFormat());
- cmd.print("Mesh export format: " + cmd.meshExportFormat());
- cmd.print("Group export format: " + cmd.hierarchyExportFormat());
- cmd.print("******************************************");
- cmd.print("[Other parameters]");
- cmd.print(QObject::tr("Silent mode: ") + (cmd.silentMode() ? "ON" : "OFF"));
- cmd.print(QObject::tr("Auto save: ") + (cmd.autoSaveMode() ? "ON" : "OFF"));
- cmd.print(QObject::tr("Auto add timestamp: ") + (cmd.addTimestamp() ? "ON" : "OFF"));
- cmd.print(QObject::tr("Numerical precision: %1").arg(cmd.numericalPrecision()));
- return true;
- }
- CommandSetVerbosity::CommandSetVerbosity()
- : ccCommandLineInterface::Command(QObject::tr("Set Verbosity"), COMMAND_VERBOSITY)
- {}
- bool CommandSetVerbosity::process(ccCommandLineInterface& cmd)
- {
- if (cmd.arguments().empty())
- {
- return cmd.error(QObject::tr("Missing parameter: verbosity level after: %1").arg(COMMAND_VERBOSITY));
- }
- bool ok = false;
- int verbosityLevel = cmd.arguments().takeFirst().toInt(&ok);
- if (!ok)
- {
- return cmd.error(QObject::tr("Invalid verbosity level %1").arg(verbosityLevel));
- }
- else
- {
- cmd.print(QObject::tr("Set verbosity level to %1").arg(verbosityLevel));
- ccLog::SetVerbosityLevel(verbosityLevel);
- }
- return true;
- }
|