| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815 |
- //##########################################################################
- //# #
- //# CLOUDCOMPARE #
- //# #
- //# This program is free software; you can redistribute it and/or modify #
- //# it under the terms of the GNU General Public License as published by #
- //# the Free Software Foundation; version 2 or later of the License. #
- //# #
- //# This program is distributed in the hope that it will be useful, #
- //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
- //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
- //# GNU General Public License for more details. #
- //# #
- //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
- //# #
- //##########################################################################
- #include "mainwindow.h"
- //CCCoreLib Includes
- #include <CloudSamplingTools.h>
- #include <Delaunay2dMesh.h>
- #include <Jacobi.h>
- #include <MeshSamplingTools.h>
- #include <NormalDistribution.h>
- #include <ParallelSort.h>
- #include <PointCloud.h>
- #include <ScalarFieldTools.h>
- #include <StatisticalTestingTools.h>
- #include <WeibullDistribution.h>
- //for tests
- #include <ChamferDistanceTransform.h>
- #include <SaitoSquaredDistanceTransform.h>
- //qCC_db
- #include <cc2DLabel.h>
- #include <cc2DViewportObject.h>
- #include <cc2DViewportLabel.h>
- #include <ccCameraSensor.h>
- #include <ccColorScalesManager.h>
- #include <ccCylinder.h>
- #include <ccFacet.h>
- #include <ccFileUtils.h>
- #include <ccGBLSensor.h>
- #include <ccImage.h>
- #include <ccKdTree.h>
- #include <ccPlane.h>
- #include <ccProgressDialog.h>
- #include <ccQuadric.h>
- #include <ccSphere.h>
- #include <ccSubMesh.h>
- //qCC_io
- #include <ccShiftAndScaleCloudDlg.h>
- #include <BinFilter.h>
- #include <AsciiFilter.h>
- #include <DepthMapFileFilter.h>
- //QCC_glWindow
- #include <ccGLWindowInterface.h>
- #include <ccRenderingTools.h>
- //local includes
- #include "ccConsole.h"
- #include "ccEntityAction.h"
- #include "ccHistogramWindow.h"
- #include "ccInnerRect2DFinder.h"
- //common
- #include <ccPickingHub.h>
- //common dialogs
- #include <ccCameraParamEditDlg.h>
- #include <ccDisplaySettingsDlg.h>
- #include <ccPickOneElementDlg.h>
- #include <ccStereoModeDlg.h>
- //dialogs
- #include "ccAboutDialog.h"
- #include "ccAdjustZoomDlg.h"
- #include "ccAlignDlg.h"
- #include "ccApplication.h"
- #include "ccApplyTransformationDlg.h"
- #include "ccAskThreeDoubleValuesDlg.h"
- #include "ccBoundingBoxEditorDlg.h"
- #include "ccCamSensorProjectionDlg.h"
- #include "ccClippingBoxTool.h"
- #include "ccColorFromScalarDlg.h"
- #include "ccColorScaleEditorDlg.h"
- #include "ccComparisonDlg.h"
- #include "ccPrimitiveDistanceDlg.h"
- #include "ccFilterByValueDlg.h"
- #include "ccGBLSensorProjectionDlg.h"
- #include "ccGeomFeaturesDlg.h"
- #include "ccGraphicalSegmentationTool.h"
- #include "ccGraphicalTransformationTool.h"
- #include "ccItemSelectionDlg.h"
- #include "ccLabelingDlg.h"
- #include "ccMatchScalesDlg.h"
- #include "ccNoiseFilterDlg.h"
- #include "ccOrderChoiceDlg.h"
- #include "ccPlaneEditDlg.h"
- #include "ccPointListPickingDlg.h"
- #include "ccPointPairRegistrationDlg.h"
- #include "ccPointPropertiesDlg.h"
- #include "ccPrimitiveFactoryDlg.h"
- #include "ccPtsSamplingDlg.h"
- #include "ccRasterizeTool.h"
- #include "ccRegistrationDlg.h"
- #include "ccRenderToFileDlg.h"
- #include "ccScaleDlg.h"
- #include "ccSectionExtractionTool.h"
- #include "ccSensorComputeDistancesDlg.h"
- #include "ccSensorComputeScatteringAnglesDlg.h"
- #include "ccSORFilterDlg.h"
- #include "ccSubsamplingDlg.h"
- #include "ccTracePolylineTool.h"
- #include "ccTranslationManager.h"
- #include "ccUnrollDlg.h"
- #include "ccVolumeCalcTool.h"
- #include "ccWaveformDialog.h"
- #include "ccEntitySelectionDlg.h"
- #include "ccSmoothPolylineDlg.h"
- //other
- #include "ccCropTool.h"
- #include "ccPersistentSettings.h"
- #include "ccRecentFiles.h"
- #include "ccRegistrationTools.h"
- #include "ccUtils.h"
- #include "db_tree/ccDBRoot.h"
- #include "pluginManager/ccPluginUIManager.h"
- #include "ccGlFilter.h"
- //3D mouse handler
- #ifdef CC_3DXWARE_SUPPORT
- #include "cc3DMouseManager.h"
- #endif
- //Gamepads
- #ifdef CC_GAMEPAD_SUPPORT
- #include "ccGamepadManager.h"
- #endif
- //Qt
- #include <QClipboard>
- #include <QGLShader>
- //Qt UI files
- #include <ui_distanceMapDlg.h>
- #include <ui_globalShiftSettingsDlg.h>
- #include <ui_mainWindow.h>
- //System
- #include <iostream>
- #include <random>
- //global static pointer (as there should only be one instance of MainWindow!)
- static MainWindow* s_instance = nullptr;
- //default file filter separator
- static const QString s_fileFilterSeparator(";;");
- enum PickingOperation { NO_PICKING_OPERATION,
- PICKING_ROTATION_CENTER,
- PICKING_LEVEL_POINTS,
- };
- static ccGLWindowInterface* s_pickingWindow = nullptr;
- static PickingOperation s_currentPickingOperation = NO_PICKING_OPERATION;
- static std::vector<cc2DLabel*> s_levelLabels;
- static ccPointCloud* s_levelMarkersCloud = nullptr;
- static ccHObject* s_levelEntity = nullptr;
- static QFileDialog::Options CCFileDialogOptions()
- {
- //dialog options
- QFileDialog::Options dialogOptions = QFileDialog::Options();
- dialogOptions |= QFileDialog::DontResolveSymlinks;
- if (!ccOptions::Instance().useNativeDialogs)
- {
- dialogOptions |= QFileDialog::DontUseNativeDialog;
- }
- return dialogOptions;
- }
- MainWindow::MainWindow()
- : m_UI( new Ui::MainWindow )
- , m_ccRoot(nullptr)
- , m_uiFrozen(false)
- , m_recentFiles(new ccRecentFiles(this))
- , m_3DMouseManager(nullptr)
- , m_gamepadManager(nullptr)
- , m_viewModePopupButton(nullptr)
- , m_pivotVisibilityPopupButton(nullptr)
- , m_firstShow(true)
- , m_pickingHub(nullptr)
- , m_cpeDlg(nullptr)
- , m_gsTool(nullptr)
- , m_tplTool(nullptr)
- , m_seTool(nullptr)
- , m_transTool(nullptr)
- , m_clipTool(nullptr)
- , m_compDlg(nullptr)
- , m_ppDlg(nullptr)
- , m_plpDlg(nullptr)
- , m_pprDlg(nullptr)
- , m_pfDlg(nullptr)
- {
- m_UI->setupUi( this );
- setWindowTitle(QStringLiteral("CloudCompare v") + ccApp->versionLongStr(false));
-
- m_pluginUIManager = new ccPluginUIManager( this, this );
-
- ccTranslationManager::Get().populateMenu( m_UI->menuLanguage, ccApp->translationPath() );
-
- #ifdef Q_OS_MAC
- m_UI->actionAbout->setMenuRole( QAction::AboutRole );
- m_UI->actionAboutPlugins->setMenuRole( QAction::ApplicationSpecificRole );
- m_UI->actionFullScreen->setText( tr( "Enter Full Screen" ) );
- m_UI->actionFullScreen->setShortcut( QKeySequence( Qt::CTRL + Qt::META + Qt::Key_F ) );
- #endif
- // Set up dynamic menus
- m_UI->menuFile->insertMenu(m_UI->actionSave, m_recentFiles->menu());
-
- //Console
- ccConsole::Init(m_UI->consoleWidget, this, this);
- m_UI->actionEnableQtWarnings->setChecked(ccConsole::QtMessagesEnabled());
- //advanced widgets not handled by QDesigner
- {
- //view mode pop-up menu
- {
- m_viewModePopupButton = new QToolButton();
- QMenu* menu = new QMenu(m_viewModePopupButton);
- menu->addAction(m_UI->actionSetOrthoView);
- menu->addAction(m_UI->actionSetCenteredPerspectiveView);
- menu->addAction(m_UI->actionSetViewerPerspectiveView);
- m_viewModePopupButton->setMenu(menu);
- m_viewModePopupButton->setPopupMode(QToolButton::InstantPopup);
- m_viewModePopupButton->setToolTip(tr("Set current view mode"));
- m_viewModePopupButton->setStatusTip(m_viewModePopupButton->toolTip());
- m_UI->toolBarView->insertWidget(m_UI->actionZoomAndCenter, m_viewModePopupButton);
- m_viewModePopupButton->setEnabled(false);
- }
- //pivot center pop-up menu
- {
- m_pivotVisibilityPopupButton = new QToolButton();
- QMenu* menu = new QMenu(m_pivotVisibilityPopupButton);
- menu->addAction(m_UI->actionSetPivotAlwaysOn);
- menu->addAction(m_UI->actionSetPivotRotationOnly);
- menu->addAction(m_UI->actionSetPivotOff);
- m_pivotVisibilityPopupButton->setMenu(menu);
- m_pivotVisibilityPopupButton->setPopupMode(QToolButton::InstantPopup);
- m_pivotVisibilityPopupButton->setToolTip(tr("Set pivot visibility"));
- m_pivotVisibilityPopupButton->setStatusTip(m_pivotVisibilityPopupButton->toolTip());
- m_UI->toolBarView->insertWidget(m_UI->actionZoomAndCenter,m_pivotVisibilityPopupButton);
- m_pivotVisibilityPopupButton->setEnabled(false);
- }
- }
- //tabifyDockWidget(DockableDBTree,DockableProperties);
- //db-tree
- {
- m_ccRoot = new ccDBRoot(m_UI->dbTreeView, m_UI->propertiesTreeView, this);
- connect(m_ccRoot, &ccDBRoot::selectionChanged, this, &MainWindow::updateUIWithSelection, Qt::QueuedConnection);
- connect(m_ccRoot, &ccDBRoot::dbIsEmpty, this, [=]() { updateUIWithSelection(); updateMenus(); }, Qt::QueuedConnection); //we don't call updateUI because there's no need to update the properties dialog
- connect(m_ccRoot, &ccDBRoot::dbIsNotEmptyAnymore, this, [=]() { updateUIWithSelection(); updateMenus(); }, Qt::QueuedConnection); //we don't call updateUI because there's no need to update the properties dialog
- }
- //MDI Area
- {
- m_mdiArea = new QMdiArea(this);
- setCentralWidget(m_mdiArea);
- connect(m_mdiArea, &QMdiArea::subWindowActivated, this, &MainWindow::updateMenus);
- connect(m_mdiArea, &QMdiArea::subWindowActivated, this, &MainWindow::on3DViewActivated);
- m_mdiArea->installEventFilter(this);
- }
- //picking hub
- {
- m_pickingHub = new ccPickingHub(this, this);
- connect(m_mdiArea, &QMdiArea::subWindowActivated, m_pickingHub, &ccPickingHub::onActiveWindowChanged);
- }
- // restore the state of the 'auto-restore' menu entry
- // (do that before connecting the actions)
- {
- QSettings settings;
- bool doNotAutoRestoreGeometry = settings.value(ccPS::DoNotRestoreWindowGeometry(), !m_UI->actionRestoreWindowOnStartup->isChecked()).toBool();
- m_UI->actionRestoreWindowOnStartup->setChecked(!doNotAutoRestoreGeometry);
- }
- connectActions();
- new3DView();
- setupInputDevices();
- freezeUI(false);
- updateUI();
- QMainWindow::statusBar()->showMessage(tr("Ready"));
-
- #ifdef CC_CORE_LIB_USES_TBB
- ccConsole::Print( QStringLiteral( "[TBB] Using Intel's Threading Building Blocks %1" )
- .arg( QString( TBB_VERSION ) ) );
- #endif
-
- ccConsole::Print(tr("CloudCompare started!"));
- }
- MainWindow::~MainWindow()
- {
- destroyInputDevices();
- cancelPreviousPickingOperation(false); //just in case
- assert(m_ccRoot && m_mdiArea);
- m_ccRoot->disconnect();
- m_mdiArea->disconnect();
- //we don't want any other dialog/function to use the following structures
- ccDBRoot* ccRoot = m_ccRoot;
- m_ccRoot = nullptr;
- //remove all entities from 3D views before quitting to avoid any side-effect
- //(this won't be done automatically since we've just reset m_ccRoot)
- ccRoot->getRootEntity()->setDisplay_recursive(nullptr);
- for (int i = 0; i < getGLWindowCount(); ++i)
- {
- getGLWindow(i)->setSceneDB(nullptr);
- }
- m_cpeDlg = nullptr;
- m_gsTool = nullptr;
- m_seTool = nullptr;
- m_transTool = nullptr;
- m_clipTool = nullptr;
- m_compDlg = nullptr;
- m_ppDlg = nullptr;
- m_plpDlg = nullptr;
- m_pprDlg = nullptr;
- m_pfDlg = nullptr;
- //release all 'overlay' dialogs
- while (!m_mdiDialogs.empty())
- {
- ccMDIDialogs mdiDialog = m_mdiDialogs.back();
- m_mdiDialogs.pop_back();
- mdiDialog.dialog->disconnect();
- mdiDialog.dialog->stop(false);
- mdiDialog.dialog->setParent(nullptr);
- delete mdiDialog.dialog;
- }
- //m_mdiDialogs.clear();
- m_mdiArea->closeAllSubWindows();
- if (ccRoot)
- {
- delete ccRoot;
- ccRoot = nullptr;
- }
- delete m_UI;
- m_UI = nullptr;
-
- ccConsole::ReleaseInstance(false); //if we flush the console, it will try to display the console window while we are destroying everything!
- }
- void MainWindow::initPlugins( )
- {
- m_pluginUIManager->init();
-
- // Set up dynamic tool bars
- addToolBar( Qt::RightToolBarArea, m_pluginUIManager->glFiltersToolbar() );
- addToolBar( Qt::RightToolBarArea, m_pluginUIManager->mainPluginToolbar() );
-
- for ( QToolBar *toolbar : m_pluginUIManager->additionalPluginToolbars() )
- {
- addToolBar( Qt::TopToolBarArea, toolbar );
- }
-
- // Set up dynamic menus
- m_UI->menubar->insertMenu( m_UI->menu3DViews->menuAction(), m_pluginUIManager->pluginMenu() );
- m_UI->menuDisplay->insertMenu( m_UI->menuActiveScalarField->menuAction(), m_pluginUIManager->shaderAndFilterMenu() );
- m_UI->menuToolbars->addAction( m_pluginUIManager->actionShowMainPluginToolbar() );
- m_UI->menuToolbars->addAction( m_pluginUIManager->actionShowGLFilterToolbar() );
- }
- void MainWindow::doEnableQtWarnings(bool state)
- {
- ccConsole::EnableQtMessages(state);
- }
- void MainWindow::increasePointSize()
- {
- //active window?
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->setPointSize(win->getViewportParameters().defaultPointSize + 1);
- win->redraw();
- }
- }
- void MainWindow::decreasePointSize()
- {
- //active window?
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->setPointSize(win->getViewportParameters().defaultPointSize - 1);
- win->redraw();
- }
- }
- void MainWindow::setupInputDevices()
- {
- #ifdef CC_3DXWARE_SUPPORT
- m_3DMouseManager = new cc3DMouseManager( this, this );
- m_UI->menuFile->insertMenu(m_UI->actionCloseAll, m_3DMouseManager->menu());
- #endif
- #ifdef CC_GAMEPAD_SUPPORT
- m_gamepadManager = new ccGamepadManager( this, this );
- m_UI->menuFile->insertMenu(m_UI->actionCloseAll, m_gamepadManager->menu());
- #endif
- #if defined(CC_3DXWARE_SUPPORT) || defined(CC_GAMEPAD_SUPPORT)
- m_UI->menuFile->insertSeparator(m_UI->actionCloseAll);
- #endif
- }
- void MainWindow::destroyInputDevices()
- {
- #ifdef CC_GAMEPAD_SUPPORT
- delete m_gamepadManager;
- m_gamepadManager = nullptr;
- #endif
- #ifdef CC_3DXWARE_SUPPORT
- delete m_3DMouseManager;
- m_3DMouseManager = nullptr;
- #endif
- }
- void MainWindow::connectActions()
- {
- assert(m_ccRoot);
- assert(m_mdiArea);
-
- //Keyboard shortcuts
-
- //'A': toggles selected items activation
- connect(m_UI->actionToggleActivation, &QAction::triggered, this, [=]() {
- toggleSelectedEntitiesProperty( ccEntityAction::TOGGLE_PROPERTY::ACTIVE );
- });
- //'V': toggles selected items visibility
- connect(m_UI->actionToggleVisibility, &QAction::triggered, this, [=]() {
- toggleSelectedEntitiesProperty( ccEntityAction::TOGGLE_PROPERTY::VISIBLE );
- });
- //'N': toggles selected items normals visibility
- connect(m_UI->actionToggleNormals, &QAction::triggered, this, [=]() {
- toggleSelectedEntitiesProperty( ccEntityAction::TOGGLE_PROPERTY::NORMALS );
- });
- //'C': toggles selected items colors visibility
- connect(m_UI->actionToggleColors, &QAction::triggered, this, [=]() {
- toggleSelectedEntitiesProperty( ccEntityAction::TOGGLE_PROPERTY::COLOR );
- });
- //'S': toggles selected items SF visibility
- connect(m_UI->actionToggleSF, &QAction::triggered, this, [=]() {
- toggleSelectedEntitiesProperty( ccEntityAction::TOGGLE_PROPERTY::SCALAR_FIELD );
- });
- //'D': toggles selected items '3D name' visibility
- connect(m_UI->actionToggleShowName, &QAction::triggered, this, [=]() {
- toggleSelectedEntitiesProperty( ccEntityAction::TOGGLE_PROPERTY::NAME );
- });
- //'M': toggles selected items materials/textures visibility
- connect(m_UI->actionToggleMaterials, &QAction::triggered, this, [=]() {
- toggleSelectedEntitiesProperty( ccEntityAction::TOGGLE_PROPERTY::MATERIAL );
- });
- //TODO... but not ready yet ;)
- m_UI->actionLoadShader->setVisible(false);
- m_UI->actionDeleteShader->setVisible(false);
- m_UI->actionKMeans->setVisible(false);
- m_UI->actionFrontPropagation->setVisible(false);
- /*** MAIN MENU ***/
- //"File" menu
- connect(m_UI->actionOpen, &QAction::triggered, this, &MainWindow::doActionLoadFile);
- connect(m_UI->actionSave, &QAction::triggered, this, &MainWindow::doActionSaveFile);
- connect(m_UI->actionSaveProject, &QAction::triggered, this, &MainWindow::doActionSaveProject);
- connect(m_UI->actionGlobalShiftSettings, &QAction::triggered, this, &MainWindow::doActionGlobalShiftSeetings);
- connect(m_UI->actionPrimitiveFactory, &QAction::triggered, this, &MainWindow::doShowPrimitiveFactory);
- connect(m_UI->actionCloseAll, &QAction::triggered, this, &MainWindow::closeAll);
- connect(m_UI->actionQuit, &QAction::triggered, this, &QWidget::close);
- //"Edit > Colors" menu
- connect(m_UI->actionSetUniqueColor, &QAction::triggered, this, &MainWindow::doActionSetUniqueColor);
- connect(m_UI->actionSetColorGradient, &QAction::triggered, this, &MainWindow::doActionSetColorGradient);
- connect(m_UI->actionChangeColorLevels, &QAction::triggered, this, &MainWindow::doActionChangeColorLevels);
- connect(m_UI->actionColorize, &QAction::triggered, this, &MainWindow::doActionColorize);
- connect(m_UI->actionRGBToGreyScale, &QAction::triggered, this, &MainWindow::doActionRGBToGreyScale);
- connect(m_UI->actionInterpolateColors, &QAction::triggered, this, &MainWindow::doActionInterpolateColors);
- connect(m_UI->actionEnhanceRGBWithIntensities, &QAction::triggered, this, &MainWindow::doActionEnhanceRGBWithIntensities);
- connect(m_UI->actionColorFromScalarField, &QAction::triggered, this, &MainWindow::doActionColorFromScalars);
- connect(m_UI->actionClearColor, &QAction::triggered, this, [=]() {
- clearSelectedEntitiesProperty( ccEntityAction::CLEAR_PROPERTY::COLORS );
- });
- connect(m_UI->actionRGBGaussianFilter, &QAction::triggered, this, &MainWindow::doActionRGBGaussianFilter);
- connect(m_UI->actionRGBBilateralFilter, &QAction::triggered, this, &MainWindow::doActionRGBBilateralFilter);
- connect(m_UI->actionRGBMeanFilter, &QAction::triggered, this, &MainWindow::doActionRGBMeanFilter);
- connect(m_UI->actionRGBMedianFilter, &QAction::triggered, this, &MainWindow::doActionRGBMedianFilter);
- //"Edit > Normals" menu
- connect(m_UI->actionComputeNormals, &QAction::triggered, this, &MainWindow::doActionComputeNormals);
- connect(m_UI->actionInvertNormals, &QAction::triggered, this, &MainWindow::doActionInvertNormals);
- connect(m_UI->actionConvertNormalToHSV, &QAction::triggered, this, &MainWindow::doActionConvertNormalsToHSV);
- connect(m_UI->actionConvertNormalToDipDir, &QAction::triggered, this, &MainWindow::doActionConvertNormalsToDipDir);
- connect(m_UI->actionExportNormalToSF, &QAction::triggered, this, &MainWindow::doActionExportNormalToSF);
- connect(m_UI->actionSetSFsAsNormal, &QAction::triggered, this, &MainWindow::doActionSetSFsAsNormal);
- connect(m_UI->actionOrientNormalsMST, &QAction::triggered, this, &MainWindow::doActionOrientNormalsMST);
- connect(m_UI->actionOrientNormalsFM, &QAction::triggered, this, &MainWindow::doActionOrientNormalsFM);
- connect(m_UI->actionShiftPointsAlongNormals, &QAction::triggered, this, &MainWindow::doActionShiftPointsAlongNormals);
- connect(m_UI->actionClearNormals, &QAction::triggered, this, [=]() {
- clearSelectedEntitiesProperty( ccEntityAction::CLEAR_PROPERTY::NORMALS );
- });
- //"Edit > Octree" menu
- connect(m_UI->actionComputeOctree, &QAction::triggered, this, &MainWindow::doActionComputeOctree);
- connect(m_UI->actionResampleWithOctree, &QAction::triggered, this, &MainWindow::doActionResampleWithOctree);
- //"Edit > Grid" menu
- connect(m_UI->actionDeleteScanGrid, &QAction::triggered, this, &MainWindow::doActionDeleteScanGrids);
- //"Edit > Cloud" menu
- connect(m_UI->actionCreateSinglePointCloud, &QAction::triggered, this, &MainWindow::createSinglePointCloud);
- connect(m_UI->actionPasteCloudFromClipboard, &QAction::triggered, this, &MainWindow::createPointCloudFromClipboard);
- //the 'Paste from clipboard' tool depends on the clipboard state
- {
- const QClipboard* clipboard = QApplication::clipboard();
- assert(clipboard);
- m_UI->actionPasteCloudFromClipboard->setEnabled(clipboard->mimeData()->hasText());
- connect(clipboard, &QClipboard::dataChanged, [&]() { m_UI->actionPasteCloudFromClipboard->setEnabled(clipboard->mimeData()->hasText()); });
- }
- //"Edit > Mesh" menu
- connect(m_UI->actionComputeMeshAA, &QAction::triggered, this, &MainWindow::doActionComputeMeshAA);
- connect(m_UI->actionComputeMeshLS, &QAction::triggered, this, &MainWindow::doActionComputeMeshLS);
- connect(m_UI->actionMeshTwoPolylines, &QAction::triggered, this, &MainWindow::doMeshTwoPolylines);
- connect(m_UI->actionMeshScanGrids, &QAction::triggered, this, &MainWindow::doActionMeshScanGrids);
- connect(m_UI->actionConvertTextureToColor, &QAction::triggered, this, &MainWindow::doActionConvertTextureToColor);
- connect(m_UI->actionSamplePointsOnMesh, &QAction::triggered, this, &MainWindow::doActionSamplePointsOnMesh);
- connect(m_UI->actionSmoothMeshLaplacian, &QAction::triggered, this, &MainWindow::doActionSmoothMeshLaplacian);
- connect(m_UI->actionSubdivideMesh, &QAction::triggered, this, &MainWindow::doActionSubdivideMesh);
- connect(m_UI->actionFlipMeshTriangles, &QAction::triggered, this, &MainWindow::doActionFlipMeshTriangles);
- connect(m_UI->actionMeasureMeshSurface, &QAction::triggered, this, &MainWindow::doActionMeasureMeshSurface);
- connect(m_UI->actionMeasureMeshVolume, &QAction::triggered, this, &MainWindow::doActionMeasureMeshVolume);
- connect(m_UI->actionFlagMeshVertices, &QAction::triggered, this, &MainWindow::doActionFlagMeshVertices);
- //"Edit > Mesh > Scalar Field" menu
- connect(m_UI->actionSmoothMeshSF, &QAction::triggered, this, &MainWindow::doActionSmoothMeshSF);
- connect(m_UI->actionEnhanceMeshSF, &QAction::triggered, this, &MainWindow::doActionEnhanceMeshSF);
- //"Edit > Polyline" menu
- connect(m_UI->actionSamplePointsOnPolyline, &QAction::triggered, this, &MainWindow::doActionSamplePointsOnPolyline);
- connect(m_UI->actionSmoothPolyline, &QAction::triggered, this, &MainWindow::doActionSmoohPolyline);
-
- //"Edit > Plane" menu
- connect(m_UI->actionCreatePlane, &QAction::triggered, this, &MainWindow::doActionCreatePlane);
- connect(m_UI->actionEditPlane, &QAction::triggered, this, &MainWindow::doActionEditPlane);
- connect(m_UI->actionFlipPlane, &QAction::triggered, this, &MainWindow::doActionFlipPlane);
- connect(m_UI->actionComparePlanes, &QAction::triggered, this, &MainWindow::doActionComparePlanes);
- //"Edit > Sensor > Ground-Based lidar" menu
- connect(m_UI->actionShowDepthBuffer, &QAction::triggered, this, &MainWindow::doActionShowDepthBuffer);
- connect(m_UI->actionExportDepthBuffer, &QAction::triggered, this, &MainWindow::doActionExportDepthBuffer);
- connect(m_UI->actionComputePointsVisibility, &QAction::triggered, this, &MainWindow::doActionComputePointsVisibility);
- //"Edit > Sensor" menu
- connect(m_UI->actionCreateGBLSensor, &QAction::triggered, this, &MainWindow::doActionCreateGBLSensor);
- connect(m_UI->actionCreateCameraSensor, &QAction::triggered, this, &MainWindow::doActionCreateCameraSensor);
- connect(m_UI->actionModifySensor, &QAction::triggered, this, &MainWindow::doActionModifySensor);
- connect(m_UI->actionProjectUncertainty, &QAction::triggered, this, &MainWindow::doActionProjectUncertainty);
- connect(m_UI->actionCheckPointsInsideFrustum, &QAction::triggered, this, &MainWindow::doActionCheckPointsInsideFrustum);
- connect(m_UI->actionComputeDistancesFromSensor, &QAction::triggered, this, &MainWindow::doActionComputeDistancesFromSensor);
- connect(m_UI->actionComputeScatteringAngles, &QAction::triggered, this, &MainWindow::doActionComputeScatteringAngles);
- connect(m_UI->actionViewFromSensor, &QAction::triggered, this, &MainWindow::doActionSetViewFromSensor);
- //"Edit > Scalar fields" menu
- connect(m_UI->actionShowHistogram, &QAction::triggered, this, &MainWindow::showSelectedEntitiesHistogram);
- connect(m_UI->actionComputeStatParams, &QAction::triggered, this, &MainWindow::doActionComputeStatParams);
- connect(m_UI->actionSFGradient, &QAction::triggered, this, &MainWindow::doActionSFGradient);
- connect(m_UI->actionGaussianFilter, &QAction::triggered, this, &MainWindow::doActionSFGaussianFilter);
- connect(m_UI->actionBilateralFilter, &QAction::triggered, this, &MainWindow::doActionSFBilateralFilter);
- connect(m_UI->actionFilterByValue, &QAction::triggered, this, &MainWindow::doActionFilterByValue);
- connect(m_UI->actionAddConstantSF, &QAction::triggered, this, &MainWindow::doActionAddConstantSF);
- connect(m_UI->actionAddClassificationSF, &QAction::triggered, this, &MainWindow::doActionAddClassificationSF);
- connect(m_UI->actionScalarFieldArithmetic, &QAction::triggered, this, &MainWindow::doActionScalarFieldArithmetic);
- connect(m_UI->actionScalarFieldFromColor, &QAction::triggered, this, &MainWindow::doActionScalarFieldFromColor);
- connect(m_UI->actionConvertToRGB, &QAction::triggered, this, &MainWindow::doActionSFConvertToRGB);
- connect(m_UI->actionConvertToRandomRGB, &QAction::triggered, this, &MainWindow::doActionSFConvertToRandomRGB);
- connect(m_UI->actionRenameSF, &QAction::triggered, this, &MainWindow::doActionRenameSF);
- connect(m_UI->actionOpenColorScalesManager, &QAction::triggered, this, &MainWindow::doActionOpenColorScalesManager);
- connect(m_UI->actionAddIdField, &QAction::triggered, this, &MainWindow::doActionAddIdField);
- connect(m_UI->actionSplitCloudUsingSF, &QAction::triggered, this, &MainWindow::doActionSplitCloudUsingSF);
- connect(m_UI->actionSetSFAsCoord, &QAction::triggered, this, &MainWindow::doActionSetSFAsCoord);
- connect(m_UI->actionInterpolateSFs, &QAction::triggered, this, &MainWindow::doActionInterpolateScalarFields);
- connect(m_UI->actionDeleteScalarField, &QAction::triggered, this, [=]() {
- clearSelectedEntitiesProperty( ccEntityAction::CLEAR_PROPERTY::CURRENT_SCALAR_FIELD );
- });
- connect(m_UI->actionDeleteAllSF, &QAction::triggered, this, [=]() {
- clearSelectedEntitiesProperty( ccEntityAction::CLEAR_PROPERTY::ALL_SCALAR_FIELDS );
- });
-
- //"Edit > Waveform" menu
- connect(m_UI->actionShowWaveDialog, &QAction::triggered, this, &MainWindow::doActionShowWaveDialog);
- connect(m_UI->actionCompressFWFData, &QAction::triggered, this, &MainWindow::doActionCompressFWFData);
- //"Edit" menu
- connect(m_UI->actionClone, &QAction::triggered, this, &MainWindow::doActionClone);
- connect(m_UI->actionMerge, &QAction::triggered, this, &MainWindow::doActionMerge);
- connect(m_UI->actionApplyTransformation, &QAction::triggered, this, &MainWindow::doActionApplyTransformation);
- connect(m_UI->actionApplyScale, &QAction::triggered, this, &MainWindow::doActionApplyScale);
- connect(m_UI->actionTranslateRotate, &QAction::triggered, this, &MainWindow::activateTranslateRotateMode);
- connect(m_UI->actionSegment, &QAction::triggered, this, &MainWindow::activateSegmentationMode);
- connect(m_UI->actionTracePolyline, &QAction::triggered, this, &MainWindow::activateTracePolylineMode);
- connect(m_UI->actionCrop, &QAction::triggered, this, &MainWindow::doActionCrop);
- connect(m_UI->actionEditGlobalShiftAndScale, &QAction::triggered, this, &MainWindow::doActionEditGlobalShiftAndScale);
- connect(m_UI->actionSubsample, &QAction::triggered, this, &MainWindow::doActionSubsample);
- connect(m_UI->actionDelete, &QAction::triggered, m_ccRoot, &ccDBRoot::deleteSelectedEntities);
- //"Tools > Clean" menu
- connect(m_UI->actionSORFilter, &QAction::triggered, this, &MainWindow::doActionSORFilter);
- connect(m_UI->actionNoiseFilter, &QAction::triggered, this, &MainWindow::doActionFilterNoise);
- //"Tools > Projection" menu
- connect(m_UI->actionUnroll, &QAction::triggered, this, &MainWindow::doActionUnroll);
- connect(m_UI->actionRasterize, &QAction::triggered, this, &MainWindow::doActionRasterize);
- connect(m_UI->actionConvertPolylinesToMesh, &QAction::triggered, this, &MainWindow::doConvertPolylinesToMesh);
- //connect(m_UI->actionCreateSurfaceBetweenTwoPolylines, &QAction::triggered, this, &MainWindow::doMeshTwoPolylines); //DGM: already connected to actionMeshTwoPolylines
- connect(m_UI->actionExportCoordToSF, &QAction::triggered, this, &MainWindow::doActionExportCoordToSF);
-
- //"Tools > Registration" menu
- connect(m_UI->actionMatchBBCenters, &QAction::triggered, this, &MainWindow::doActionMatchBBCenters);
- connect(m_UI->actionMatchScales, &QAction::triggered, this, &MainWindow::doActionMatchScales);
- connect(m_UI->actionRegister, &QAction::triggered, this, &MainWindow::doActionRegister);
- connect(m_UI->actionPointPairsAlign, &QAction::triggered, this, &MainWindow::activateRegisterPointPairTool);
- connect(m_UI->actionBBCenterToOrigin, &QAction::triggered, this, &MainWindow::doActionMoveBBCenterToOrigin);
- connect(m_UI->actionBBMinCornerToOrigin, &QAction::triggered, this, &MainWindow::doActionMoveBBMinCornerToOrigin);
- connect(m_UI->actionBBMaxCornerToOrigin, &QAction::triggered, this, &MainWindow::doActionMoveBBMaxCornerToOrigin);
- //"Tools > Distances" menu
- connect(m_UI->actionCloudCloudDist, &QAction::triggered, this, &MainWindow::doActionCloudCloudDist);
- connect(m_UI->actionCloudMeshDist, &QAction::triggered, this, &MainWindow::doActionCloudMeshDist);
- connect(m_UI->actionCloudPrimitiveDist, &QAction::triggered, this, &MainWindow::doActionCloudPrimitiveDist);
- connect(m_UI->actionCPS, &QAction::triggered, this, &MainWindow::doActionComputeCPS);
- //"Tools > Volume" menu
- connect(m_UI->actionCompute2HalfDimVolume, &QAction::triggered, this, &MainWindow::doCompute2HalfDimVolume);
- //"Tools > Statistics" menu
- connect(m_UI->actionComputeStatParams2, &QAction::triggered, this, &MainWindow::doActionComputeStatParams); //duplicated action --> we can't use the same otherwise we get an ugly console warning on Linux :(
- connect(m_UI->actionStatisticalTest, &QAction::triggered, this, &MainWindow::doActionStatisticalTest);
- //"Tools > Segmentation" menu
- connect(m_UI->actionLabelConnectedComponents, &QAction::triggered, this, &MainWindow::doActionLabelConnectedComponents);
- connect(m_UI->actionKMeans, &QAction::triggered, this, &MainWindow::doActionKMeans);
- connect(m_UI->actionFrontPropagation, &QAction::triggered, this, &MainWindow::doActionFrontPropagation);
- connect(m_UI->actionCrossSection, &QAction::triggered, this, &MainWindow::activateClippingBoxMode);
- connect(m_UI->actionExtractSections, &QAction::triggered, this, &MainWindow::activateSectionExtractionMode);
- //"Tools > Fit" menu
- connect(m_UI->actionFitPlane, &QAction::triggered, this, &MainWindow::doActionFitPlane);
- connect(m_UI->actionFitSphere, &QAction::triggered, this, &MainWindow::doActionFitSphere);
- connect(m_UI->actionFitCircle, &QAction::triggered, this, &MainWindow::doActionFitCircle);
- connect(m_UI->actionFitFacet, &QAction::triggered, this, &MainWindow::doActionFitFacet);
- connect(m_UI->actionFitQuadric, &QAction::triggered, this, &MainWindow::doActionFitQuadric);
- //"Tools > Batch export" menu
- connect(m_UI->actionExportCloudInfo, &QAction::triggered, this, &MainWindow::doActionExportCloudInfo);
- connect(m_UI->actionExportPlaneInfo, &QAction::triggered, this, &MainWindow::doActionExportPlaneInfo);
- //"Tools > Other" menu
- connect(m_UI->actionComputeGeometricFeature, &QAction::triggered, this, &MainWindow::doComputeGeometricFeature);
- connect(m_UI->actionRemoveDuplicatePoints, &QAction::triggered, this, &MainWindow::doRemoveDuplicatePoints);
- //"Tools"
- connect(m_UI->actionLevel, &QAction::triggered, this, &MainWindow::doLevel);
- connect(m_UI->actionPointListPicking, &QAction::triggered, this, &MainWindow::activatePointListPickingMode);
- connect(m_UI->actionPointPicking, &QAction::triggered, this, &MainWindow::activatePointPickingMode);
- //"Tools > Sand box (research)" menu
- connect(m_UI->actionComputeKdTree, &QAction::triggered, this, &MainWindow::doActionComputeKdTree);
- connect(m_UI->actionDistanceMap, &QAction::triggered, this, &MainWindow::doActionComputeDistanceMap);
- connect(m_UI->actionDistanceToBestFitQuadric3D, &QAction::triggered, this, &MainWindow::doActionComputeDistToBestFitQuadric3D);
- connect(m_UI->actionComputeBestFitBB, &QAction::triggered, this, &MainWindow::doComputeBestFitBB);
- connect(m_UI->actionAlign, &QAction::triggered, this, &MainWindow::doAction4pcsRegister); //Aurelien BEY le 13/11/2008
- connect(m_UI->actionSNETest, &QAction::triggered, this, &MainWindow::doSphericalNeighbourhoodExtractionTest);
- connect(m_UI->actionCNETest, &QAction::triggered, this, &MainWindow::doCylindricalNeighbourhoodExtractionTest);
- connect(m_UI->actionFindBiggestInnerRectangle, &QAction::triggered, this, &MainWindow::doActionFindBiggestInnerRectangle);
- connect(m_UI->actionCreateCloudFromEntCenters, &QAction::triggered, this, &MainWindow::doActionCreateCloudFromEntCenters);
- connect(m_UI->actionComputeBestICPRmsMatrix, &QAction::triggered, this, &MainWindow::doActionComputeBestICPRmsMatrix);
- //"Display" menu
- connect(m_UI->actionFullScreen, &QAction::toggled, this, &MainWindow::toggleFullScreen);
- connect(m_UI->actionExclusiveFullScreen, &QAction::toggled, this, &MainWindow::toggleExclusiveFullScreen);
- connect(m_UI->actionRefresh, &QAction::triggered, this, &MainWindow::refreshAll);
- connect(m_UI->actionTestFrameRate, &QAction::triggered, this, &MainWindow::testFrameRate);
- connect(m_UI->actionToggleCenteredPerspective, &QAction::triggered, this, &MainWindow::toggleActiveWindowCenteredPerspective);
- connect(m_UI->actionToggleViewerBasedPerspective, &QAction::triggered, this, &MainWindow::toggleActiveWindowViewerBasedPerspective);
- connect(m_UI->actionShowCursor3DCoordinates, &QAction::toggled, this, &MainWindow::toggleActiveWindowShowCursorCoords);
- connect(m_UI->actionLockRotationAxis, &QAction::triggered, this, &MainWindow::toggleLockRotationAxis);
- connect(m_UI->actionEnterBubbleViewMode, &QAction::triggered, this, &MainWindow::doActionEnableBubbleViewMode);
- connect(m_UI->actionEditCamera, &QAction::triggered, this, &MainWindow::doActionEditCamera);
- connect(m_UI->actionAdjustZoom, &QAction::triggered, this, &MainWindow::doActionAdjustZoom);
- connect(m_UI->actionSaveViewportAsObject, &QAction::triggered, this, &MainWindow::doActionSaveViewportAsCamera);
- //"Display > Lights & Materials" menu
- connect(m_UI->actionDisplaySettings, &QAction::triggered, this, &MainWindow::showDisplaySettings);
- connect(m_UI->actionToggleSunLight, &QAction::triggered, this, &MainWindow::toggleActiveWindowSunLight);
- connect(m_UI->actionToggleCustomLight, &QAction::triggered, this, &MainWindow::toggleActiveWindowCustomLight);
- connect(m_UI->actionRenderToFile, &QAction::triggered, this, &MainWindow::doActionRenderToFile);
- //"Display > Shaders & filters" menu
- connect(m_UI->actionLoadShader, &QAction::triggered, this, &MainWindow::doActionLoadShader);
- connect(m_UI->actionDeleteShader, &QAction::triggered, this, &MainWindow::doActionDeleteShader);
- //"Display > Active SF" menu
- connect(m_UI->actionToggleActiveSFColorScale, &QAction::triggered, this, &MainWindow::doActionToggleActiveSFColorScale);
- connect(m_UI->actionShowActiveSFPrevious, &QAction::triggered, this, &MainWindow::doActionShowActiveSFPrevious);
- connect(m_UI->actionShowActiveSFNext, &QAction::triggered, this, &MainWindow::doActionShowActiveSFNext);
- //"Display" menu
- connect(m_UI->actionResetGUIElementsPos, &QAction::triggered, this, &MainWindow::doActionResetGUIElementsPos);
- connect(m_UI->actionRestoreWindowOnStartup, &QAction::toggled, this, &MainWindow::doActionToggleRestoreWindowOnStartup);
- connect(m_UI->actionResetAllVBOs, &QAction::triggered, this, &MainWindow::doActionResetAllVBOs);
- //"3D Views" menu
- connect(m_UI->menu3DViews, &QMenu::aboutToShow, this, &MainWindow::update3DViewsMenu);
- connect(m_UI->actionNew3DView, &QAction::triggered, this, &MainWindow::new3DView);
- connect(m_UI->actionZoomIn, &QAction::triggered, this, &MainWindow::zoomIn);
- connect(m_UI->actionZoomOut, &QAction::triggered, this, &MainWindow::zoomOut);
- connect(m_UI->actionClose3DView, &QAction::triggered, m_mdiArea, &QMdiArea::closeActiveSubWindow);
- connect(m_UI->actionCloseAll3DViews, &QAction::triggered, m_mdiArea, &QMdiArea::closeAllSubWindows);
- connect(m_UI->actionTile3DViews, &QAction::triggered, m_mdiArea, &QMdiArea::tileSubWindows);
- connect(m_UI->actionCascade3DViews, &QAction::triggered, m_mdiArea, &QMdiArea::cascadeSubWindows);
- connect(m_UI->actionNext3DView, &QAction::triggered, m_mdiArea, &QMdiArea::activateNextSubWindow);
- connect(m_UI->actionPrevious3DView, &QAction::triggered, m_mdiArea, &QMdiArea::activatePreviousSubWindow);
- //"About" menu entry
- connect(m_UI->actionHelp, &QAction::triggered, this, &MainWindow::doActionShowHelpDialog);
- connect(m_UI->actionAboutPlugins, &QAction::triggered, m_pluginUIManager, &ccPluginUIManager::showAboutDialog);
- connect(m_UI->actionEnableQtWarnings, &QAction::toggled, this, &MainWindow::doEnableQtWarnings);
- connect(m_UI->actionAbout, &QAction::triggered, this, [this] () {
- ccAboutDialog* aboutDialog = new ccAboutDialog(this);
- aboutDialog->exec();
- });
- /*** Toolbars ***/
- //View toolbar
- connect(m_UI->actionGlobalZoom, &QAction::triggered, this, &MainWindow::setGlobalZoom);
- connect(m_UI->actionPickRotationCenter, &QAction::triggered, this, &MainWindow::doPickRotationCenter);
- connect(m_UI->actionZoomAndCenter, &QAction::triggered, this, &MainWindow::zoomOnSelectedEntities);
- connect(m_UI->actionSetPivotAlwaysOn, &QAction::triggered, this, &MainWindow::setPivotAlwaysOn);
- connect(m_UI->actionSetPivotRotationOnly, &QAction::triggered, this, &MainWindow::setPivotRotationOnly);
- connect(m_UI->actionSetPivotOff, &QAction::triggered, this, &MainWindow::setPivotOff);
-
- connect(m_UI->actionSetOrthoView, &QAction::triggered, this, [this] () {
- setOrthoView( getActiveGLWindow() );
- });
- connect(m_UI->actionSetCenteredPerspectiveView, &QAction::triggered, this, [this] () {
- setCenteredPerspectiveView( getActiveGLWindow() );
- });
- connect(m_UI->actionSetViewerPerspectiveView, &QAction::triggered, this, [this] () {
- setViewerPerspectiveView( getActiveGLWindow() );
- });
-
- connect(m_UI->actionEnableStereo, &QAction::toggled, this, &MainWindow::toggleActiveWindowStereoVision);
- connect(m_UI->actionAutoPickRotationCenter, &QAction::toggled, this, &MainWindow::toggleActiveWindowAutoPickRotCenter);
-
- connect(m_UI->actionSetViewTop, &QAction::triggered, this, [=]() { setView( CC_TOP_VIEW ); });
- connect(m_UI->actionSetViewBottom, &QAction::triggered, this, [=]() { setView( CC_BOTTOM_VIEW ); });
- connect(m_UI->actionSetViewFront, &QAction::triggered, this, [=]() { setView( CC_FRONT_VIEW ); });
- connect(m_UI->actionSetViewBack, &QAction::triggered, this, [=]() { setView( CC_BACK_VIEW ); });
- connect(m_UI->actionSetViewLeft, &QAction::triggered, this, [=]() { setView( CC_LEFT_VIEW ); });
- connect(m_UI->actionSetViewRight, &QAction::triggered, this, [=]() { setView( CC_RIGHT_VIEW ); });
- connect(m_UI->actionSetViewIso1, &QAction::triggered, this, [=]() { setView( CC_ISO_VIEW_1 ); });
- connect(m_UI->actionSetViewIso2, &QAction::triggered, this, [=]() { setView( CC_ISO_VIEW_2 ); });
-
- //hidden
- connect(m_UI->actionEnableVisualDebugTraces, &QAction::triggered, this, &MainWindow::toggleVisualDebugTraces);
- }
- void MainWindow::doActionColorize()
- {
- doActionSetColor(true);
- }
- void MainWindow::doActionSetUniqueColor()
- {
- doActionSetColor(false);
- }
- void MainWindow::doActionSetColor(bool colorize)
- {
- if ( !ccEntityAction::setColor(m_selectedEntities, colorize, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionRGBToGreyScale()
- {
- if ( !ccEntityAction::rgbToGreyScale( m_selectedEntities ) )
- return;
- refreshAll();
- }
- void MainWindow::doActionSetColorGradient()
- {
- if ( !ccEntityAction::setColorGradient(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionChangeColorLevels()
- {
- ccEntityAction::changeColorLevels(m_selectedEntities, this);
- }
- void MainWindow::doActionInterpolateColors()
- {
- if ( !ccEntityAction::interpolateColors(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionInterpolateScalarFields()
- {
- if (!ccEntityAction::interpolateSFs(m_selectedEntities, this))
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionEnhanceRGBWithIntensities()
- {
- if (!ccEntityAction::enhanceRGBWithIntensities(m_selectedEntities, this))
- return;
- refreshAll();
- }
- void MainWindow::doActionColorFromScalars()
- {
- for (ccHObject *entity : getSelectedEntities())
- {
- //for "real" point clouds only
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- if (cloud)
- {
- //create color from scalar dialogue
- ccColorFromScalarDlg* cfsDlg = new ccColorFromScalarDlg(this, cloud);
- cfsDlg->setAttribute(Qt::WA_DeleteOnClose, true);
- cfsDlg->show();
- }
- }
- }
- void MainWindow::doActionInvertNormals()
- {
- if ( !ccEntityAction::invertNormals(m_selectedEntities) )
- return;
- refreshAll();
- }
- void MainWindow::doActionConvertNormalsToDipDir()
- {
- if ( !ccEntityAction::convertNormalsTo( m_selectedEntities,
- ccEntityAction::NORMAL_CONVERSION_DEST::DIP_DIR_SFS) )
- {
- return;
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionConvertNormalsToHSV()
- {
- if ( !ccEntityAction::convertNormalsTo( m_selectedEntities,
- ccEntityAction::NORMAL_CONVERSION_DEST::HSV_COLORS) )
- {
- return;
- }
- refreshAll();
- updateUI();
- }
- static double s_kdTreeMaxErrorPerCell = 0.1;
- void MainWindow::doActionComputeKdTree()
- {
- ccGenericPointCloud* cloud = nullptr;
- if ( haveOneSelection() )
- {
- ccHObject* ent = m_selectedEntities.front();
- bool lockedVertices;
- cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), true);
- return;
- }
- }
- if (!cloud)
- {
- ccLog::Error(tr("Selected one and only one point cloud or mesh!"));
- return;
- }
- bool ok;
- s_kdTreeMaxErrorPerCell = QInputDialog::getDouble(this, tr("Compute Kd-tree"), tr("Max error per leaf cell:"), s_kdTreeMaxErrorPerCell, 1.0e-6, 1.0e6, 6, &ok);
- if (!ok)
- return;
- ccProgressDialog pDlg(true, this);
- //computation
- QElapsedTimer eTimer;
- eTimer.start();
- ccKdTree* kdtree = new ccKdTree(cloud);
- if (kdtree->build(s_kdTreeMaxErrorPerCell, CCCoreLib::DistanceComputationTools::MAX_DIST_95_PERCENT, 4, 1000, &pDlg))
- {
- qint64 elapsedTime_ms = eTimer.elapsed();
- ccConsole::Print("[doActionComputeKdTree] Timing: %2.3f s", elapsedTime_ms / 1.0e3);
- cloud->setEnabled(true); //for mesh vertices!
- cloud->addChild(kdtree);
- kdtree->setDisplay(cloud->getDisplay());
- kdtree->setVisible(true);
- kdtree->prepareDisplayForRefresh();
- #ifdef QT_DEBUG
- kdtree->convertCellIndexToSF();
- #else
- kdtree->convertCellIndexToRandomColor();
- #endif
- addToDB(kdtree);
- refreshAll();
- updateUI();
- }
- else
- {
- ccLog::Error(tr("An error occurred"));
- delete kdtree;
- kdtree = nullptr;
- }
- }
- void MainWindow::doActionComputeOctree()
- {
- if ( !ccEntityAction::computeOctree(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionResampleWithOctree()
- {
- bool ok;
- int pointCount = QInputDialog::getInt(this, tr("Resample with octree"), tr("Points (approx.)"), 1000000, 1, INT_MAX, 100000, &ok);
- if (!ok)
- return;
- ccProgressDialog pDlg(false, this);
- pDlg.setAutoClose(false);
- assert(pointCount > 0);
- unsigned aimedPoints = static_cast<unsigned>(pointCount);
- bool errors = false;
- for ( ccHObject *entity : getSelectedEntities() )
- {
- ccPointCloud* cloud = nullptr;
- /*if (ent->isKindOf(CC_TYPES::MESH)) //TODO
- cloud = ccHObjectCaster::ToGenericMesh(ent)->getAssociatedCloud();
- else */
- if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- cloud = static_cast<ccPointCloud*>(entity);
- }
- if (cloud)
- {
- ccOctree::Shared octree = cloud->getOctree();
- if (!octree)
- {
- octree = cloud->computeOctree(&pDlg);
- if (!octree)
- {
- ccConsole::Error(tr("Could not compute octree for cloud '%1'").arg(cloud->getName()));
- continue;
- }
- }
- cloud->setEnabled(false);
- QElapsedTimer eTimer;
- eTimer.start();
- CCCoreLib::GenericIndexedCloud* result = CCCoreLib::CloudSamplingTools::resampleCloudWithOctree
- (
- cloud,
- aimedPoints,
- CCCoreLib::CloudSamplingTools::CELL_GRAVITY_CENTER,
- &pDlg,
- octree.data()
- );
- if (result)
- {
- ccConsole::Print("[ResampleWithOctree] Timing: %3.2f s.", eTimer.elapsed() / 1.0e3);
- ccPointCloud* newCloud = ccPointCloud::From(result, cloud);
- delete result;
- result = nullptr;
- if (newCloud)
- {
- addToDB(newCloud);
- newCloud->setDisplay(cloud->getDisplay());
- newCloud->prepareDisplayForRefresh();
- }
- else
- {
- errors = true;
- }
- }
- }
- }
- if (errors)
- ccLog::Error(tr("[ResampleWithOctree] Errors occurred during the process, result may be incomplete"));
- refreshAll();
- }
- void MainWindow::doActionApplyTransformation()
- {
- ccApplyTransformationDlg dlg(this);
- if (!dlg.exec())
- return;
- bool applyToGlobal = false;
- ccGLMatrixd transMat = dlg.getTransformation(applyToGlobal);
- applyTransformation(transMat, applyToGlobal);
- }
- ccHObject::Container MainWindow::getTopLevelSelectedEntities() const
- {
- ccHObject::Container topLevelSelectedEntities;
- for (size_t i = 0; i < m_selectedEntities.size(); ++i)
- {
- ccHObject* entity = m_selectedEntities[i];
- bool hasParentsInselection = false;
- for (size_t j = 0; j < m_selectedEntities.size(); ++j)
- {
- if (i == j)
- continue;
- ccHObject* otherEntity = m_selectedEntities[j];
- if (otherEntity->isAncestorOf(entity))
- {
- hasParentsInselection = true;
- break;
- }
- }
- if (!hasParentsInselection)
- {
- topLevelSelectedEntities.push_back(entity);
- }
- }
- return topLevelSelectedEntities;
- }
- void MainWindow::applyTransformation(const ccGLMatrixd& mat, bool applyToGlobal)
- {
- //if the transformation is partly converted to global shift/scale
- bool autoApplyPreviousGlobalShiftAndScale = false;
- double previousScale = 1.0;
- CCVector3d previousShift(0, 0, 0);
- //we don't want any entity that would be the children of other selected entities
- ccHObject::Container selectedEntities = getTopLevelSelectedEntities();
- for (ccHObject* entity : selectedEntities) //warning, getSelectedEntites may change during this loop!
- {
- ccGLMatrixd transMat = mat;
- if (applyToGlobal && nullptr != dynamic_cast<ccShiftedObject*>(entity))
- {
- // the user wants to apply the transformation to the global coordinates
- ccShiftedObject* shiftedEntity = static_cast<ccShiftedObject*>(entity);
- CCVector3d globalShift = shiftedEntity->getGlobalShift();
- double globalScale = shiftedEntity->getGlobalScale();
- // we compute the impact to the local coordinate system without changing the
- // current Global Shift & Scale parameters (for now)
- // Here is the formula, assuming:
- // - the Global Shift is Ts
- // - the Global scale is Sc
- // - the transformation is (R, T)
- // - the Global point coordinates Pg are derived from the local ones Pl with: Pg = Pl/Sc - Ts
- // Therefore, Pg' = R * Pg + T
- // i.e. Pg' = R.(Pl/Sc - Ts) + T
- // i.e. Pg' = (R.Pl)/Sc - R.Ts + T
- // i.e. Pg' = (R.Pl)/Sc - R.Ts + T + Ts - Ts
- // i.e. Pg' = (R.Pl + Sc.[Ts - R.Ts + T])/Sc - Ts
- // i.e. Pl' = Sc.[Ts -R.Ts + T]
- // i.e. the translation of the 'local' coordinate system is: Sc.[T + Ts - R.Ts]
- CCVector3d rotatedGlobalShift = globalShift;
- mat.applyRotation(rotatedGlobalShift);
- CCVector3d localTranslation = globalScale * (globalShift - rotatedGlobalShift + mat.getTranslationAsVec3D());
- // we switch to a local transformation matrix
- transMat.setTranslation(localTranslation);
- }
- //we don't test primitives (it's always ok while the 'vertices lock' test would fail)
- if (!entity->isKindOf(CC_TYPES::PRIMITIVE))
- {
- //specific test for locked vertices
- bool lockedVertices;
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity, &lockedVertices);
- if (cloud)
- {
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(entity->getName(), haveOneSelection());
- continue;
- }
- //test if the translated cloud coordinates were already "too large"
- //(in which case we won't bother the user about the fact that the transformed cloud coordinates will be too large...)
- ccBBox localBBox = entity->getOwnBB();
- CCVector3d Pl = localBBox.minCorner();
- double Dl = localBBox.getDiagNormd();
- //if the cloud is alright
- if ( !ccGlobalShiftManager::NeedShift(Pl)
- && !ccGlobalShiftManager::NeedRescale(Dl) )
- {
- //test if the translated cloud (local) coordinates are too large
- ccBBox transformedLocalBox = entity->getOwnBB() * transMat;
- CCVector3d transformedPl = transformedLocalBox.minCorner();
- double transformedDl = transformedLocalBox.getDiagNormd();
- bool needShift = ccGlobalShiftManager::NeedShift(transformedPl) || ccGlobalShiftManager::NeedRescale(transformedDl);
- if (needShift)
- {
- //existing shift information
- CCVector3d globalShift = cloud->getGlobalShift();
- double globalScale = cloud->getGlobalScale();
- //we compute the global coordinates and scale of the reference point (= the min corner of the bounding-box)
- CCVector3d Pg = cloud->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;
- bool updateShiftAndscale = false;
- //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;
- updateShiftAndscale = true;
- }
- }
- //if we still need to define new Global Shift and Scale
- if (needShift)
- {
- //ask the user the right values!
- ccShiftAndScaleCloudDlg sasDlg(transformedPl, transformedDl, Pg, Dg, this);
- sasDlg.showApplyAllButton(selectedEntities.size() > 1);
- sasDlg.showTitle(true);
- sasDlg.setKeepGlobalPos(true);
- sasDlg.showKeepGlobalPosCheckbox(false); //we don't want the user to mess with this!
- sasDlg.showPreserveShiftOnSave(true);
- //add "original" entry
- int index = sasDlg.addShiftInfo(ccGlobalShiftManager::ShiftInfo(tr("Original"), globalShift, globalScale));
- //add "previous" entry (if any)
- if (autoApplyPreviousGlobalShiftAndScale)
- {
- index = sasDlg.addShiftInfo(ccGlobalShiftManager::ShiftInfo(tr("Previous"), previousShift, previousScale));
- }
- //add "last" entries (if any)
- int matchingIndex = -1;
- const auto& previousEntries = ccGlobalShiftManager::GetLast();
- for (const ccGlobalShiftManager::ShiftInfo& shiftInfo : previousEntries)
- {
- index = sasDlg.addShiftInfo(shiftInfo);
- if (matchingIndex < 0)
- {
- if ( !ccGlobalShiftManager::NeedShift(Pg + shiftInfo.shift)
- && !ccGlobalShiftManager::NeedRescale(Dg * shiftInfo.scale) )
- {
- matchingIndex = index;
- }
- }
- }
- //if no good solution was found...
- if (matchingIndex < 0)
- {
- //add a "suggested" entry
- CCVector3d suggestedShift = ccGlobalShiftManager::BestShift(Pg);
- double suggestedScale = ccGlobalShiftManager::BestScale(Dg);
- matchingIndex = sasDlg.addShiftInfo(ccGlobalShiftManager::ShiftInfo(tr("Suggested"), suggestedShift, suggestedScale));
- }
- sasDlg.setCurrentProfile(matchingIndex);
- if (sasDlg.exec())
- {
- newScale = sasDlg.getScale();
- newShift = sasDlg.getShift();
- needShift = false;
- updateShiftAndscale = true;
- //store the shift for next time!
- ccGlobalShiftManager::StoreShift(newShift, newScale);
- if (sasDlg.applyAll())
- {
- autoApplyPreviousGlobalShiftAndScale = true;
- previousScale = newScale;
- previousShift = newShift;
- }
- }
- else if (sasDlg.cancelled())
- {
- ccLog::Warning(tr("[ApplyTransformation] Process cancelled by user"));
- return;
- }
- else
- {
- // the user did not want to change the shift & scale
- }
- }
- if (updateShiftAndscale)
- {
- assert(!needShift);
- //get the relative modification to existing global shift/scale info
- assert(globalScale != 0);
- double scaleChange = newScale / globalScale;
- CCVector3d shiftChange = newShift - globalShift;
- if (scaleChange != 1.0 || shiftChange.norm2() != 0)
- {
- //apply translation as global shift
- cloud->setGlobalShift(newShift);
- cloud->setGlobalScale(newScale);
- ccLog::Warning(tr("[ApplyTransformation] Cloud '%1' global shift/scale information has been updated: shift = (%2,%3,%4) / scale = %5").arg(cloud->getName()).arg(newShift.x).arg(newShift.y).arg(newShift.z).arg(newScale));
- transMat.scaleRotation(scaleChange);
- transMat.setTranslation(transMat.getTranslationAsVec3D() + newScale * shiftChange);
- }
- }
- }
- }
- }
- }
- //we temporarily detach the entity, as it may undergo
- //'severe' modifications (octree deletion, etc.) --> see ccHObject::applyRigidTransformation
- ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(entity);
- entity->setGLTransformation(ccGLMatrix(transMat.data()));
- //DGM FIXME: we only test the entity own bounding box (and we update its shift & scale info) but we apply the transformation to all its children?!
- entity->applyGLTransformation_recursive();
- entity->prepareDisplayForRefresh_recursive();
- putObjectBackIntoDBTree(entity, objContext);
- if (applyToGlobal)
- {
- ccLog::Print(tr("[ApplyTransformation] Transformation matrix applied to the local coordinates of %1:").arg(entity->getName()));
- ccLog::Print(transMat.toString(12, ' ')); //full precision
- }
- }
- if (!applyToGlobal)
- {
- ccLog::Print(tr("[ApplyTransformation] Applied transformation matrix:"));
- }
- else
- {
- ccLog::Print(tr("[ApplyTransformation] Global transformation matrix:"));
- }
- ccLog::Print(mat.toString(12, ' ')); //full precision
- ccLog::Print(tr("Hint: you can copy a transformation matrix (CTRL+C) and apply it - or its inverse - to another entity with the 'Edit > Apply transformation' tool"));
- //reselect previously selected entities!
- if (m_ccRoot)
- m_ccRoot->selectEntities(selectedEntities);
- zoomOnSelectedEntities();
- refreshAll();
- }
- void MainWindow::doActionApplyScale()
- {
- ccScaleDlg dlg(this);
- if (!dlg.exec())
- return;
- dlg.saveState();
- //save values for next time
- CCVector3d scales = dlg.getScales();
- bool keepInPlace = dlg.keepInPlace();
- bool rescaleGlobalShift = dlg.rescaleGlobalShift();
- //we must backup 'm_selectedEntities' as removeObjectTemporarilyFromDBTree can modify it!
- ccHObject::Container selectedEntities = m_selectedEntities;
- //first check that all coordinates are kept 'small'
- std::vector< std::pair<ccHObject*, ccGenericPointCloud*> > candidates;
- {
- bool testBigCoordinates = true;
- for (ccHObject *entity : selectedEntities) //warning, getSelectedEntites may change during this loop!
- {
- bool lockedVertices;
- //try to get the underlying cloud (or the vertices set for a mesh)
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity, &lockedVertices);
- //otherwise we can look if the selected entity is a polyline
- if (!cloud && entity->isA(CC_TYPES::POLY_LINE))
- {
- cloud = dynamic_cast<ccGenericPointCloud*>(static_cast<ccPolyline*>(entity)->getAssociatedCloud());
- if (!cloud || cloud->isAncestorOf(entity))
- {
- lockedVertices = true;
- }
- }
- if (!cloud || !cloud->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccLog::Warning(tr("[Apply scale] Entity '%1' can't be scaled this way").arg(entity->getName()));
- continue;
- }
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(entity->getName(), haveOneSelection());
- continue;
- }
- CCVector3 C(0, 0, 0);
- if (keepInPlace)
- {
- C = cloud->getOwnBB().getCenter();
- }
- //we must check that the resulting cloud coordinates are not too big
- if (testBigCoordinates)
- {
- ccBBox bbox = cloud->getOwnBB();
- CCVector3 bbMin = bbox.minCorner();
- CCVector3 bbMax = bbox.maxCorner();
- double maxx = std::max(std::abs(bbMin.x), std::abs(bbMax.x));
- double maxy = std::max(std::abs(bbMin.y), std::abs(bbMax.y));
- double maxz = std::max(std::abs(bbMin.z), std::abs(bbMax.z));
- const double maxCoord = ccGlobalShiftManager::MaxCoordinateAbsValue();
- bool oldCoordsWereTooBig = ( maxx > maxCoord
- || maxy > maxCoord
- || maxz > maxCoord );
- if (!oldCoordsWereTooBig)
- {
- maxx = std::max(std::abs((bbMin.x - C.x) * scales.x + C.x), std::abs((bbMax.x - C.x) * scales.x + C.x));
- maxy = std::max(std::abs((bbMin.y - C.y) * scales.y + C.y), std::abs((bbMax.y - C.y) * scales.y + C.y));
- maxz = std::max(std::abs((bbMin.z - C.z) * scales.z + C.z), std::abs((bbMax.z - C.z) * scales.z + C.z));
- bool newCoordsAreTooBig = ( maxx > maxCoord
- || maxy > maxCoord
- || maxz > maxCoord );
- if (newCoordsAreTooBig)
- {
- if (QMessageBox::question(
- this,
- tr("Big coordinates"),
- tr("Resutling coordinates will be too big (original precision may be lost!). Proceed anyway?"),
- QMessageBox::Yes,
- QMessageBox::No) == QMessageBox::Yes)
- {
- //ok, we won't test anymore and proceed
- testBigCoordinates = false;
- }
- else
- {
- //we stop the process
- return;
- }
- }
- }
- }
- assert(cloud);
- candidates.emplace_back(entity, cloud);
- }
- }
- if (candidates.empty())
- {
- ccConsole::Warning(tr("[Apply scale] No eligible entities (point clouds or meshes) were selected!"));
- return;
- }
- //now do the real scaling work
- {
- for ( auto& candidate : candidates )
- {
- ccHObject* ent = candidate.first;
- ccGenericPointCloud* cloud = candidate.second;
- CCVector3 C(0, 0, 0);
- if (keepInPlace)
- {
- C = cloud->getOwnBB().getCenter();
- }
- //we temporarily detach the entity, as it may undergo
- //'severe' modifications (octree deletion, etc.) --> see ccPointCloud::scale
- ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(cloud);
- cloud->scale( static_cast<PointCoordinateType>(scales.x),
- static_cast<PointCoordinateType>(scales.y),
- static_cast<PointCoordinateType>(scales.z),
- C );
- putObjectBackIntoDBTree(cloud, objContext);
- cloud->prepareDisplayForRefresh_recursive();
- //don't forget the 'global shift'!
- //DGM: but not the global scale!
- if (rescaleGlobalShift)
- {
- const CCVector3d& shift = cloud->getGlobalShift();
- cloud->setGlobalShift( CCVector3d( shift.x*scales.x,
- shift.y*scales.y,
- shift.z*scales.z) );
- }
- //specific case for polyline vertices
- if (cloud->getParent() && cloud->getParent()->isA(CC_TYPES::POLY_LINE))
- {
- ccPolyline* poly = static_cast<ccPolyline*>(cloud->getParent());
- if (poly->getAssociatedCloud() == cloud)
- {
- poly->invalidateBoundingBox();
- }
- }
- ent->prepareDisplayForRefresh_recursive();
- }
- }
- //reselect previously selected entities!
- if (m_ccRoot)
- m_ccRoot->selectEntities(selectedEntities);
- if (!keepInPlace)
- zoomOnSelectedEntities();
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionEditGlobalShiftAndScale()
- {
- //get the global shift/scale info and bounding box of all selected clouds
- std::vector< std::pair<ccShiftedObject*, ccHObject*> > shiftedEntities;
- CCVector3d Pl(0, 0, 0);
- double Dl = 1.0;
- CCVector3d Pg(0, 0, 0);
- double Dg = 1.0;
- //shift and scale (if unique)
- CCVector3d shift(0, 0, 0);
- double scale = 1.0;
- {
- bool uniqueShift = true;
- bool uniqueScale = true;
- ccBBox localBB;
- //sadly we don't have a double-typed bounding box class yet ;)
- CCVector3d globalBBmin(0, 0, 0);
- CCVector3d globalBBmax(0, 0, 0);
- for ( ccHObject *entity : getSelectedEntities() )
- {
- bool lockedVertices;
- ccShiftedObject* shifted = ccHObjectCaster::ToShifted(entity, &lockedVertices);
- if (!shifted)
- {
- continue;
- }
- //for (unlocked) entities only
- if (lockedVertices)
- {
- //get the vertices
- ccGenericPointCloud* vertices = nullptr;
- //if it's a mesh
- if (entity->isKindOf(CC_TYPES::MESH))
- {
- vertices = static_cast<ccGenericMesh*>(entity)->getAssociatedCloud();
- }
- else if (entity->isKindOf(CC_TYPES::POLY_LINE))
- {
- vertices = dynamic_cast<ccGenericPointCloud*>(static_cast<ccPolyline*>(entity)->getAssociatedCloud());
- }
- if (!vertices || !entity->isAncestorOf(vertices))
- {
- ccUtils::DisplayLockedVerticesWarning(entity->getName(), haveOneSelection());
- continue;
- }
- entity = vertices;
- }
- CCVector3 Al = entity->getOwnBB().minCorner();
- CCVector3 Bl = entity->getOwnBB().maxCorner();
- CCVector3d Ag = shifted->toGlobal3d<PointCoordinateType>(Al);
- CCVector3d Bg = shifted->toGlobal3d<PointCoordinateType>(Bl);
- //update local BB
- localBB.add(Al);
- localBB.add(Bl);
- //update global BB
- if (shiftedEntities.empty())
- {
- globalBBmin = Ag;
- globalBBmax = Bg;
- shift = shifted->getGlobalShift();
- uniqueScale = shifted->getGlobalScale();
- }
- else
- {
- globalBBmin = CCVector3d( std::min(globalBBmin.x, Ag.x),
- std::min(globalBBmin.y, Ag.y),
- std::min(globalBBmin.z, Ag.z) );
- globalBBmax = CCVector3d( std::max(globalBBmax.x, Bg.x),
- std::max(globalBBmax.y, Bg.y),
- std::max(globalBBmax.z, Bg.z) );
- if (uniqueShift)
- {
- uniqueShift = CCCoreLib::LessThanEpsilon((shifted->getGlobalShift() - shift).norm());
- }
- if (uniqueScale)
- {
- uniqueScale = CCCoreLib::LessThanEpsilon(std::abs(shifted->getGlobalScale() - scale));
- }
- }
- shiftedEntities.emplace_back(shifted, entity);
- }
- Pg = globalBBmin;
- Dg = (globalBBmax - globalBBmin).norm();
- Pl = localBB.minCorner();
- Dl = (localBB.maxCorner() - localBB.minCorner()).normd();
- if (!uniqueShift)
- shift = Pl - Pg;
- if (!uniqueScale)
- scale = Dg / Dl;
- }
- if (shiftedEntities.empty())
- {
- return;
- }
- ccShiftAndScaleCloudDlg sasDlg(Pl, Dl, Pg, Dg, this);
- sasDlg.showApplyAllButton(shiftedEntities.size() > 1);
- sasDlg.showApplyButton(shiftedEntities.size() == 1);
- sasDlg.showNoButton(false);
- sasDlg.setShiftFieldsPrecision(6);
- //add "original" entry
- int index = sasDlg.addShiftInfo(ccGlobalShiftManager::ShiftInfo(tr("Original"), shift, scale));
- sasDlg.setCurrentProfile(index);
- //add "last" entries (if any)
- sasDlg.addShiftInfo(ccGlobalShiftManager::GetLast());
- if (!sasDlg.exec())
- return;
- shift = sasDlg.getShift();
- scale = sasDlg.getScale();
- bool preserveGlobalPos = sasDlg.keepGlobalPos();
- ccLog::Print(tr("[Global Shift/Scale] New shift: (%1, %2, %3)").arg(shift.x).arg(shift.y).arg(shift.z));
- ccLog::Print(tr("[Global Shift/Scale] New scale: %1").arg(scale));
- //apply new shift
- {
- for ( auto &entity : shiftedEntities )
- {
- ccShiftedObject* shifted = entity.first;
- ccHObject* ent = entity.second;
- if (preserveGlobalPos)
- {
- //to preserve the global position of the cloud, we may have to translate and/or rescale the cloud
- CCVector3d Ql = ent->getOwnBB().minCorner();
- CCVector3d Qg = shifted->toGlobal3d(Ql);
- CCVector3d Ql2 = Qg * scale + shift;
- CCVector3d T = Ql2 - Ql;
- assert(shifted->getGlobalScale() > 0);
- double scaleCoef = scale / shifted->getGlobalScale();
- if ( CCCoreLib::GreaterThanEpsilon( T.norm() )
- || CCCoreLib::GreaterThanEpsilon( std::abs(scaleCoef - 1.0) ) )
- {
- ccGLMatrix transMat;
- transMat.toIdentity();
- transMat.scaleRotation(static_cast<float>(scaleCoef));
- transMat.setTranslation(T);
- //DGM FIXME: we only test the entity own bounding box (and we update its shift & scale info) but we apply the transformation to all its children?!
- ent->applyGLTransformation_recursive(&transMat);
- ent->prepareDisplayForRefresh_recursive();
- ccLog::Warning(tr("[Global Shift/Scale] To preserve its original position, the entity '%1' has been translated of (%2 ; %3 ; %4) and rescaled of a factor %5")
- .arg(ent->getName())
- .arg(T.x)
- .arg(T.y)
- .arg(T.z)
- .arg(scaleCoef));
- }
- }
- shifted->setGlobalShift(shift);
- shifted->setGlobalScale(scale);
- }
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doComputeBestFitBB()
- {
- if (QMessageBox::warning( this,
- tr("This method is for test purpose only"),
- tr("Cloud(s) are going to be rotated while still displayed in their previous position! Proceed?"),
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::No ) != QMessageBox::Yes)
- {
- return;
- }
- //backup selected entities as removeObjectTemporarilyFromDBTree can modify them
- ccHObject::Container selectedEntities = getSelectedEntities();
- for (ccHObject *entity : selectedEntities) //warning, getSelectedEntites may change during this loop!
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity);
- if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD)) // TODO
- {
- CCCoreLib::Neighbourhood Yk(cloud);
- CCCoreLib::SquareMatrixd covMat = Yk.computeCovarianceMatrix();
- if (covMat.isValid())
- {
- CCCoreLib::SquareMatrixd eigVectors;
- std::vector<double> eigValues;
- if (CCCoreLib::Jacobi<double>::ComputeEigenValuesAndVectors(covMat, eigVectors, eigValues, true))
- {
- CCCoreLib::Jacobi<double>::SortEigenValuesAndVectors(eigVectors, eigValues);
- ccGLMatrix trans;
- GLfloat* rotMat = trans.data();
- for (unsigned j = 0; j < 3; ++j)
- {
- double u[3];
- CCCoreLib::Jacobi<double>::GetEigenVector(eigVectors, j, u);
- CCVector3 v(static_cast<PointCoordinateType>(u[0]),
- static_cast<PointCoordinateType>(u[1]),
- static_cast<PointCoordinateType>(u[2]));
- v.normalize();
- rotMat[j*4] = static_cast<float>(v.x);
- rotMat[j*4+1] = static_cast<float>(v.y);
- rotMat[j*4+2] = static_cast<float>(v.z);
- }
- const CCVector3* G = Yk.getGravityCenter();
- assert(G);
- trans.shiftRotationCenter(*G);
- cloud->setGLTransformation(trans);
- trans.invert();
- //we temporarily detach the entity, as it may undergo
- //'severe' modifications (octree deletion, etc.) --> see ccPointCloud::applyRigidTransformation
- ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(cloud);
- static_cast<ccPointCloud*>(cloud)->applyRigidTransformation(trans);
- putObjectBackIntoDBTree(cloud,objContext);
- entity->prepareDisplayForRefresh_recursive();
- }
- }
- }
- }
- refreshAll();
- }
- void MainWindow::doActionFlagMeshVertices()
- {
- bool errors = false;
- bool success = false;
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isKindOf(CC_TYPES::MESH))
- {
- ccGenericMesh* mesh = ccHObjectCaster::ToGenericMesh(entity);
- ccPointCloud* vertices = ccHObjectCaster::ToPointCloud(mesh ? mesh->getAssociatedCloud() : nullptr);
- if (mesh && vertices)
- {
- //prepare a new scalar field
- int sfIdx = vertices->getScalarFieldIndexByName(CC_DEFAULT_MESH_VERT_FLAGS_SF_NAME);
- if (sfIdx < 0)
- {
- sfIdx = vertices->addScalarField(CC_DEFAULT_MESH_VERT_FLAGS_SF_NAME);
- if (sfIdx < 0)
- {
- ccConsole::Warning(tr("Not enough memory to flag the vertices of mesh '%1'!").arg(mesh->getName()));
- errors = true;
- continue;
- }
- }
- CCCoreLib::ScalarField* flags = vertices->getScalarField(sfIdx);
- CCCoreLib::MeshSamplingTools::EdgeConnectivityStats stats;
- if (CCCoreLib::MeshSamplingTools::flagMeshVerticesByType(mesh,flags,&stats))
- {
- vertices->setCurrentDisplayedScalarField(sfIdx);
- ccScalarField* sf = vertices->getCurrentDisplayedScalarField();
- if (sf)
- {
- sf->setColorScale(ccColorScalesManager::GetDefaultScale(ccColorScalesManager::VERTEX_QUALITY));
- //sf->setColorRampSteps(3); //ugly :(
- }
- vertices->showSF(true);
- mesh->showSF(true);
- mesh->prepareDisplayForRefresh_recursive();
- success = true;
- //display stats in the Console as well
- ccConsole::Print(tr("[Mesh Quality] Mesh '%1' edges: %2 total (normal: %3 / on hole borders: %4 / non-manifold: %5)").arg(entity->getName()).arg(stats.edgesCount).arg(stats.edgesSharedByTwo).arg(stats.edgesNotShared).arg(stats.edgesSharedByMore));
- }
- else
- {
- vertices->deleteScalarField(sfIdx);
- sfIdx = -1;
- ccConsole::Warning(tr("Not enough memory to flag the vertices of mesh '%1'!").arg(mesh->getName()));
- errors = true;
- }
- }
- else
- {
- assert(false);
- }
- }
- }
- refreshAll();
- updateUI();
- if (success)
- {
- //display reminder
- forceConsoleDisplay();
- ccConsole::Print(tr("[Mesh Quality] SF flags: %1 (NORMAL) / %2 (BORDER) / (%3) NON-MANIFOLD").arg(CCCoreLib::MeshSamplingTools::VERTEX_NORMAL).arg(CCCoreLib::MeshSamplingTools::VERTEX_BORDER).arg(CCCoreLib::MeshSamplingTools::VERTEX_NON_MANIFOLD));
- }
- if (errors)
- {
- ccConsole::Error(tr("Error(s) occurred! Check the console..."));
- }
- }
- void MainWindow::doActionMeasureMeshVolume()
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isKindOf(CC_TYPES::MESH))
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(entity);
- if (mesh)
- {
- //we compute the mesh volume
- double V = CCCoreLib::MeshSamplingTools::computeMeshVolume(mesh);
- //we force the console to display itself
- forceConsoleDisplay();
- ccConsole::Print(tr("[Mesh Volume] Mesh '%1': V=%2 (cube units)").arg(entity->getName()).arg(V));
- //check that the mesh is closed
- CCCoreLib::MeshSamplingTools::EdgeConnectivityStats stats;
- if (CCCoreLib::MeshSamplingTools::computeMeshEdgesConnectivity(mesh, stats))
- {
- if (stats.edgesNotShared != 0)
- {
- ccConsole::Warning(tr("[Mesh Volume] The above volume might be invalid (mesh has holes)"));
- }
- else if (stats.edgesSharedByMore != 0)
- {
- ccConsole::Warning(tr("[Mesh Volume] The above volume might be invalid (mesh has non-manifold edges)"));
- }
- }
- else
- {
- ccConsole::Warning(tr("[Mesh Volume] The above volume might be invalid (not enough memory to check if the mesh is closed)"));
- }
- }
- else
- {
- assert(false);
- }
- }
- }
- }
- void MainWindow::doActionMeasureMeshSurface()
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isKindOf(CC_TYPES::MESH))
- {
- ccGenericMesh* mesh = ccHObjectCaster::ToGenericMesh(entity);
- if (mesh)
- {
- double S = CCCoreLib::MeshSamplingTools::computeMeshArea(mesh);
- //we force the console to display itself
- forceConsoleDisplay();
- ccConsole::Print(tr("[Mesh Surface] Mesh '%1': S=%2 (square units)").arg(entity->getName()).arg(S));
- if (mesh->size())
- {
- ccConsole::Print(tr("[Mesh Surface] Average triangle surface: %1 (square units)").arg(S / double(mesh->size())));
- }
- }
- else
- {
- assert(false);
- }
- }
- }
- }
- void MainWindow::doActionComputeDistancesFromSensor()
- {
- //we support more than just one sensor in selection
- if (!haveSelection())
- {
- ccConsole::Error(tr("Select at least one sensor"));
- return;
- }
- //start dialog
- ccSensorComputeDistancesDlg cdDlg(this);
- if (!cdDlg.exec())
- return;
- for ( ccHObject *entity : getSelectedEntities() )
- {
- ccSensor* sensor = ccHObjectCaster::ToSensor( entity );
- assert(sensor);
- if (!sensor)
- continue; //skip this entity
- //get associated cloud
- ccHObject* defaultCloud = sensor->getParent() && sensor->getParent()->isA(CC_TYPES::POINT_CLOUD) ? sensor->getParent() : nullptr;
- ccPointCloud* cloud = askUserToSelectACloud(defaultCloud, tr("Select a cloud on which to project the uncertainty:"));
- if (!cloud)
- {
- return;
- }
- //sensor center
- CCVector3 sensorCenter;
- if (!sensor->getActiveAbsoluteCenter(sensorCenter))
- return;
- //squared required?
- bool squared = cdDlg.computeSquaredDistances();
- //set up a new scalar field
- const char* defaultRangesSFname = squared ? CC_DEFAULT_SQUARED_RANGES_SF_NAME : CC_DEFAULT_RANGES_SF_NAME;
- int sfIdx = cloud->getScalarFieldIndexByName(defaultRangesSFname);
- if (sfIdx < 0)
- {
- sfIdx = cloud->addScalarField(defaultRangesSFname);
- if (sfIdx < 0)
- {
- ccConsole::Error(tr("Not enough memory!"));
- return;
- }
- }
- CCCoreLib::ScalarField* distances = cloud->getScalarField(sfIdx);
- for (unsigned i = 0; i < cloud->size(); ++i)
- {
- const CCVector3* P = cloud->getPoint(i);
- ScalarType s = static_cast<ScalarType>(squared ? (*P-sensorCenter).norm2() : (*P-sensorCenter).norm());
- distances->setValue(i, s);
- }
- distances->computeMinAndMax();
- cloud->setCurrentDisplayedScalarField(sfIdx);
- cloud->showSF(true);
- cloud->prepareDisplayForRefresh_recursive();
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionComputeScatteringAngles()
- {
- //there should be only one sensor in current selection!
- if (!haveOneSelection() || !m_selectedEntities.front()->isKindOf(CC_TYPES::GBL_SENSOR))
- {
- ccConsole::Error(tr("Select one and only one GBL sensor!"));
- return;
- }
- ccSensor* sensor = ccHObjectCaster::ToSensor(m_selectedEntities.front());
- assert(sensor);
- //sensor center
- CCVector3 sensorCenter;
- if (!sensor->getActiveAbsoluteCenter(sensorCenter))
- return;
- //get associated cloud
- ccHObject* defaultCloud = sensor->getParent() && sensor->getParent()->isA(CC_TYPES::POINT_CLOUD) ? sensor->getParent() : nullptr;
- ccPointCloud* cloud = askUserToSelectACloud(defaultCloud, tr("Select a cloud on which to project the uncertainty:"));
- if (!cloud)
- {
- return;
- }
- if (!cloud->hasNormals())
- {
- ccConsole::Error(tr("The cloud must have normals!"));
- return;
- }
- ccSensorComputeScatteringAnglesDlg cdDlg(this);
- if (!cdDlg.exec())
- return;
- bool toDegreeFlag = cdDlg.anglesInDegrees();
- //prepare a new scalar field
- const char* defaultScatAnglesSFname = toDegreeFlag ? CC_DEFAULT_DEG_SCATTERING_ANGLES_SF_NAME : CC_DEFAULT_RAD_SCATTERING_ANGLES_SF_NAME;
- int sfIdx = cloud->getScalarFieldIndexByName(defaultScatAnglesSFname);
- if (sfIdx < 0)
- {
- sfIdx = cloud->addScalarField(defaultScatAnglesSFname);
- if (sfIdx < 0)
- {
- ccConsole::Error(tr("Not enough memory!"));
- return;
- }
- }
- CCCoreLib::ScalarField* angles = cloud->getScalarField(sfIdx);
- //perform computations
- for (unsigned i = 0; i < cloud->size(); ++i)
- {
- //the point position
- const CCVector3* P = cloud->getPoint(i);
- //build the ray
- CCVector3 ray = *P - sensorCenter;
- ray.normalize();
- //get the current normal
- CCVector3 normal(cloud->getPointNormal(i));
- //normal.normalize(); //should already be the case!
- //compute the angle
- PointCoordinateType cosTheta = ray.dot(normal);
- ScalarType theta = std::acos(std::min(std::abs(cosTheta), 1.0f));
- if (toDegreeFlag)
- {
- theta = CCCoreLib::RadiansToDegrees( theta );
- }
-
- angles->setValue(i,theta);
- }
- angles->computeMinAndMax();
- cloud->setCurrentDisplayedScalarField(sfIdx);
- cloud->showSF(true);
- cloud->prepareDisplayForRefresh_recursive();
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSetViewFromSensor()
- {
- //there should be only one sensor in current selection!
- if (!haveOneSelection() || !m_selectedEntities.front()->isKindOf(CC_TYPES::SENSOR))
- {
- ccConsole::Error(tr("Select one and only one sensor!"));
- return;
- }
- ccSensor* sensor = ccHObjectCaster::ToSensor(m_selectedEntities.front());
- assert(sensor);
- //try to find the associated window
- ccGenericGLDisplay* win = sensor->getDisplay();
- if (!win)
- {
- //get associated cloud
- ccPointCloud * cloud = ccHObjectCaster::ToPointCloud(sensor->getParent());
- if (cloud)
- win = cloud->getDisplay();
- }
- if (sensor->applyViewport(win))
- {
- ccConsole::Print(tr("[DoActionSetViewFromSensor] Viewport applied"));
- }
- }
- void MainWindow::doActionCreateGBLSensor()
- {
- ccGBLSensorProjectionDlg spDlg(this);
- spDlg.initWithPrevious();
- if (!spDlg.exec())
- return;
- spDlg.saveForNextTime();
- //We create the corresponding sensor for each input cloud (in a perfect world, there should be only one ;)
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity);
- //we create a new sensor
- ccGBLSensor* sensor = new ccGBLSensor();
- //we init its parameters with the dialog
- spDlg.updateGBLSensor(sensor);
- //we compute projection
- if (sensor->computeAutoParameters(cloud))
- {
- cloud->addChild(sensor);
- //we try to guess the sensor relative size (dirty)
- ccBBox bb = cloud->getOwnBB();
- double diag = bb.getDiagNorm();
- if (diag < 1.0)
- sensor->setGraphicScale(static_cast<PointCoordinateType>(1.0e-3));
- else if (diag > 10000.0)
- sensor->setGraphicScale(static_cast<PointCoordinateType>(1.0e3));
- //we display depth buffer
- int errorCode;
- if (sensor->computeDepthBuffer(cloud,errorCode))
- {
- ccRenderingTools::ShowDepthBuffer(sensor,this);
- }
- else
- {
- ccConsole::Error(ccGBLSensor::GetErrorString(errorCode));
- }
- ////DGM: test
- //{
- // //add positions
- // const unsigned count = 1000;
- // const PointCoordinateType R = 100;
- // const PointCoordinateType dh = 100;
- // for (unsigned i=0; i<1000; ++i)
- // {
- // float angle = (float)i/(float)count * 6 * M_PI;
- // float X = R * cos(angle);
- // float Y = R * sin(angle);
- // float Z = (float)i/(float)count * dh;
- // ccIndexedTransformation trans;
- // trans.initFromParameters(-angle,CCVector3(0,0,1),CCVector3(X,Y,Z));
- // sensor->addPosition(trans,i);
- // }
- //}
- //set position
- //ccIndexedTransformation trans;
- //sensor->addPosition(trans,0);
- ccGLWindowInterface* win = static_cast<ccGLWindowInterface*>(cloud->getDisplay());
- if (win)
- {
- sensor->setDisplay_recursive(win);
- sensor->setVisible(true);
- ccBBox box = cloud->getOwnBB();
- win->updateConstellationCenterAndZoom(&box);
- }
- addToDB(sensor);
- }
- else
- {
- ccLog::Error(tr("Failed to create sensor"));
- delete sensor;
- sensor = nullptr;
- }
- }
- }
- updateUI();
- }
- void MainWindow::doActionCreateCameraSensor()
- {
- //we create the camera sensor
- ccCameraSensor* sensor = new ccCameraSensor();
- ccHObject* ent = nullptr;
- if (haveSelection())
- {
- assert(haveOneSelection());
- ent = m_selectedEntities.front();
- }
- //we try to guess the sensor relative size (dirty)
- if (ent && ent->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent);
- ccBBox bb = cloud->getOwnBB();
- double diag = bb.getDiagNorm();
- if (diag < 1.0)
- sensor->setGraphicScale(static_cast<PointCoordinateType>(1.0e-3));
- else if (diag > 10000.0)
- sensor->setGraphicScale(static_cast<PointCoordinateType>(1.0e3));
- //set position
- ccIndexedTransformation trans;
- sensor->addPosition(trans, 0);
- }
- ccCamSensorProjectionDlg spDlg(this);
- //spDlg.initWithCamSensor(sensor); //DGM: we'd better leave the default parameters of the dialog!
- if (!spDlg.exec())
- {
- delete sensor;
- return;
- }
- spDlg.updateCamSensor(sensor);
- ccGLWindowInterface* win = nullptr;
- if (ent)
- {
- ent->addChild(sensor);
- win = static_cast<ccGLWindowInterface*>(ent->getDisplay());
- }
- else
- {
- win = getActiveGLWindow();
- }
- if (win)
- {
- sensor->setDisplay(win);
- sensor->setVisible(true);
- if (ent)
- {
- ccBBox box = ent->getOwnBB();
- win->updateConstellationCenterAndZoom(&box);
- }
- }
- addToDB(sensor);
- updateUI();
- }
- void MainWindow::doActionModifySensor()
- {
- //there should be only one point cloud with sensor in current selection!
- if (!haveOneSelection() || !m_selectedEntities.front()->isKindOf(CC_TYPES::SENSOR))
- {
- ccConsole::Error(tr("Select one and only one sensor!"));
- return;
- }
- ccSensor* sensor = static_cast<ccSensor*>(m_selectedEntities.front());
- //Ground based laser sensors
- if (sensor->isA(CC_TYPES::GBL_SENSOR))
- {
- ccGBLSensor* gbl = static_cast<ccGBLSensor*>(sensor);
- ccGBLSensorProjectionDlg spDlg(this);
- spDlg.initWithGBLSensor(gbl);
- if (!spDlg.exec())
- return;
- //we update its parameters
- spDlg.updateGBLSensor(gbl);
- //we re-project the associated cloud (if any)
- if (gbl->getParent() && gbl->getParent()->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(gbl->getParent());
- int errorCode;
- if (gbl->computeDepthBuffer(cloud,errorCode))
- {
- //we display depth buffer
- ccRenderingTools::ShowDepthBuffer(gbl,this);
- }
- else
- {
- ccConsole::Error(ccGBLSensor::GetErrorString(errorCode));
- }
- }
- else
- {
- //ccConsole::Warning(tr("Internal error: sensor ('%1') parent is not a point cloud!").arg(sensor->getName()));
- }
- }
- //Camera sensors
- else if (sensor->isA(CC_TYPES::CAMERA_SENSOR))
- {
- ccCameraSensor* cam = static_cast<ccCameraSensor*>(sensor);
- ccCamSensorProjectionDlg spDlg(this);
- spDlg.initWithCamSensor(cam);
- if (!spDlg.exec())
- return;
- //we update its parameters
- spDlg.updateCamSensor(cam);
- }
- else
- {
- ccConsole::Error(tr("Can't modify this kind of sensor!"));
- return;
- }
- if (sensor->isVisible() && sensor->isEnabled())
- {
- sensor->prepareDisplayForRefresh();
- refreshAll();
- }
- updateUI();
- }
- void MainWindow::doActionProjectUncertainty()
- {
- //there should only be one sensor in the current selection!
- if (!haveOneSelection() || !m_selectedEntities.front()->isKindOf(CC_TYPES::CAMERA_SENSOR))
- {
- ccConsole::Error(tr("Select one and only one camera (projective) sensor!"));
- return;
- }
- ccCameraSensor* sensor = ccHObjectCaster::ToCameraSensor(m_selectedEntities.front());
- if (!sensor)
- {
- assert(false);
- return;
- }
- const ccCameraSensor::LensDistortionParameters::Shared& distParams = sensor->getDistortionParameters();
- if (!distParams || distParams->getModel() != ccCameraSensor::BROWN_DISTORTION)
- {
- ccLog::Error(tr("Sensor has no associated uncertainty model! (Brown, etc.)"));
- return;
- }
- //we need a cloud to project the uncertainty on!
- ccHObject* defaultCloud = sensor->getParent() && sensor->getParent()->isA(CC_TYPES::POINT_CLOUD) ? sensor->getParent() : nullptr;
- ccPointCloud* pointCloud = askUserToSelectACloud(defaultCloud, tr("Select a cloud on which to project the uncertainty:"));
- if (!pointCloud)
- {
- return;
- }
- CCCoreLib::ReferenceCloud points(pointCloud);
- if (!points.reserve(pointCloud->size()))
- {
- ccConsole::Error(tr("Not enough memory!"));
- return;
- }
- points.addPointIndex(0,pointCloud->size());
- // compute uncertainty
- std::vector< Vector3Tpl<ScalarType> > accuracy;
- if (!sensor->computeUncertainty(&points, accuracy/*, false*/))
- {
- ccConsole::Error(tr("Not enough memory!"));
- return;
- }
- /////////////
- // SIGMA D //
- /////////////
- const char dimChar[3] = {'x','y','z'};
- for (unsigned d = 0; d < 3; ++d)
- {
- // add scalar field
- QString sfName = tr("[%1] Uncertainty (%2)").arg(sensor->getName()).arg(dimChar[d]);
- int sfIdx = pointCloud->getScalarFieldIndexByName(sfName.toStdString());
- if (sfIdx < 0)
- sfIdx = pointCloud->addScalarField(sfName.toStdString());
- if (sfIdx < 0)
- {
- ccLog::Error(tr("An error occurred! (see console)"));
- return;
- }
- // fill scalar field
- CCCoreLib::ScalarField* sf = pointCloud->getScalarField(sfIdx);
- assert(sf);
- if (sf)
- {
- unsigned count = static_cast<unsigned>(accuracy.size());
- assert(count == pointCloud->size());
- for (unsigned i = 0; i < count; i++)
- sf->setValue(i, accuracy[i].u[d]);
- sf->computeMinAndMax();
- }
- }
- /////////////////
- // SIGMA TOTAL //
- /////////////////
- // add scalar field
- {
- QString sfName = tr("[%1] Uncertainty (3D)").arg(sensor->getName());
- int sfIdx = pointCloud->getScalarFieldIndexByName(sfName.toStdString());
- if (sfIdx < 0)
- sfIdx = pointCloud->addScalarField(sfName.toStdString());
- if (sfIdx < 0)
- {
- ccLog::Error(tr("An error occurred! (see console)"));
- return;
- }
- // fill scalar field
- CCCoreLib::ScalarField* sf = pointCloud->getScalarField(sfIdx);
- assert(sf);
- if (sf)
- {
- unsigned count = static_cast<unsigned>(accuracy.size());
- assert(count == pointCloud->size());
- for (unsigned i = 0; i < count; i++)
- sf->setValue(i, accuracy[i].norm());
- sf->computeMinAndMax();
- }
- pointCloud->showSF(true);
- pointCloud->setCurrentDisplayedScalarField(sfIdx);
- pointCloud->prepareDisplayForRefresh();
- }
- refreshAll();
- }
- void MainWindow::doActionCheckPointsInsideFrustum()
- {
- //there should be only one camera sensor in the current selection!
- if (!haveOneSelection() || !m_selectedEntities.front()->isKindOf(CC_TYPES::CAMERA_SENSOR))
- {
- ccConsole::Error(tr("Select one and only one camera sensor!"));
- return;
- }
- ccCameraSensor* sensor = ccHObjectCaster::ToCameraSensor(m_selectedEntities.front());
- if (!sensor)
- return;
- //we need a cloud to filter!
- ccHObject* defaultCloud = sensor->getParent() && sensor->getParent()->isA(CC_TYPES::POINT_CLOUD) ? sensor->getParent() : nullptr;
- ccPointCloud* pointCloud = askUserToSelectACloud(defaultCloud, tr("Select a cloud to filter:"));
- if (!pointCloud)
- {
- return;
- }
- //comupte/get the point cloud's octree
- ccOctree::Shared octree = pointCloud->getOctree();
- if (!octree)
- {
- octree = pointCloud->computeOctree();
- if (!octree)
- {
- ccConsole::Error(tr("Failed to compute the octree!"));
- return;
- }
- }
- assert(octree);
- // filter octree then project the points
- std::vector<unsigned> inCameraFrustum;
- if (!octree->intersectWithFrustum(sensor, inCameraFrustum))
- {
- ccConsole::Error(tr("Failed to intersect sensor frustum with octree!"));
- }
- else
- {
- // scalar field
- const char sfName[] = "Frustum visibility";
- int sfIdx = pointCloud->getScalarFieldIndexByName(sfName);
- if (inCameraFrustum.empty())
- {
- ccConsole::Error(tr("No point fell inside the frustum!"));
- if (sfIdx >= 0)
- pointCloud->deleteScalarField(sfIdx);
- }
- else
- {
- if (sfIdx < 0)
- sfIdx = pointCloud->addScalarField(sfName);
- if (sfIdx < 0)
- {
- ccLog::Error(tr("Failed to allocate memory for output scalar field!"));
- return;
- }
- CCCoreLib::ScalarField* sf = pointCloud->getScalarField(sfIdx);
- assert(sf);
- if (sf)
- {
- sf->fill(0);
- const ScalarType c_insideValue = static_cast<ScalarType>(1);
- for ( unsigned index : inCameraFrustum )
- {
- sf->setValue(index, c_insideValue);
- }
- sf->computeMinAndMax();
- pointCloud->setCurrentDisplayedScalarField(sfIdx);
- pointCloud->showSF(true);
- pointCloud->redrawDisplay();
- }
- }
- }
- updateUI();
- }
- void MainWindow::doActionShowDepthBuffer()
- {
- if (!haveSelection())
- return;
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isKindOf(CC_TYPES::GBL_SENSOR))
- {
- ccGBLSensor* sensor = static_cast<ccGBLSensor*>(m_selectedEntities.front());
- if (sensor->getDepthBuffer().zBuff.empty())
- {
- //look for depending cloud
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity->getParent());
- if (cloud)
- {
- //force depth buffer computation
- int errorCode;
- if (!sensor->computeDepthBuffer(cloud, errorCode))
- {
- ccConsole::Error(ccGBLSensor::GetErrorString(errorCode));
- }
- }
- else
- {
- ccConsole::Error(tr("Internal error: sensor ('%1') parent is not a point cloud!").arg(sensor->getName()));
- return;
- }
- }
- ccRenderingTools::ShowDepthBuffer(sensor, this);
- }
- }
- }
- void MainWindow::doActionExportDepthBuffer()
- {
- if (!haveSelection())
- return;
- //persistent settings
- QSettings settings;
- settings.beginGroup(ccPS::SaveFile());
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- QString filename = QFileDialog::getSaveFileName(this,
- tr("Select output file"),
- currentPath,
- DepthMapFileFilter::GetFileFilter(),
- nullptr,
- CCFileDialogOptions()
- );
- if (filename.isEmpty())
- {
- //process cancelled by user
- return;
- }
- //save last saving location
- settings.setValue(ccPS::CurrentPath(),QFileInfo(filename).absolutePath());
- settings.endGroup();
- ccHObject* toSave = nullptr;
- bool multEntities = false;
- if (haveOneSelection())
- {
- toSave = m_selectedEntities.front();
- }
- else
- {
- toSave = new ccHObject("Temp Group");
- for ( ccHObject *entity : getSelectedEntities() )
- {
- toSave->addChild(entity,ccHObject::DP_NONE);
- }
- multEntities = true;
- }
- DepthMapFileFilter::SaveParameters parameters;
- {
- parameters.alwaysDisplaySaveDialog = true;
- }
- CC_FILE_ERROR result = DepthMapFileFilter().saveToFile(toSave, filename, parameters);
- if (result != CC_FERR_NO_ERROR)
- {
- FileIOFilter::DisplayErrorMessage(result, tr("saving"), filename);
- }
- else
- {
- ccLog::Print(tr("[I/O] File '%1' saved successfully").arg(filename));
- }
- if (multEntities)
- {
- delete toSave;
- toSave = nullptr;
- }
- }
- void MainWindow::doActionComputePointsVisibility()
- {
- //there should be only one camera sensor in the current selection!
- if (!haveOneSelection() || !m_selectedEntities.front()->isKindOf(CC_TYPES::GBL_SENSOR))
- {
- ccConsole::Error(tr("Select one and only one GBL/TLS sensor!"));
- return;
- }
- ccGBLSensor* sensor = ccHObjectCaster::ToGBLSensor(m_selectedEntities.front());
- if (!sensor)
- return;
- //we need a cloud to filter!
- ccHObject* defaultCloud = sensor->getParent() && sensor->getParent()->isA(CC_TYPES::POINT_CLOUD) ? sensor->getParent() : nullptr;
- ccPointCloud* pointCloud = askUserToSelectACloud(defaultCloud, tr("Select a cloud to filter:"));
- if (!pointCloud)
- {
- return;
- }
- if (sensor->getDepthBuffer().zBuff.empty())
- {
- if (defaultCloud)
- {
- //the sensor has no depth buffer, we'll ask the user if he wants to compute it first
- if (QMessageBox::warning( this,
- tr("Depth buffer"),
- tr("Sensor has no depth buffer: do you want to compute it now?"),
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::Yes ) == QMessageBox::No)
- {
- //we can stop then...
- return;
- }
- int errorCode;
- if (sensor->computeDepthBuffer(static_cast<ccPointCloud*>(defaultCloud), errorCode))
- {
- ccRenderingTools::ShowDepthBuffer(sensor, this);
- }
- else
- {
- ccConsole::Error(ccGBLSensor::GetErrorString(errorCode));
- return;
- }
- }
- else
- {
- ccConsole::Error(tr("Sensor has no depth buffer (and no associated cloud?)"));
- return;
- }
- }
- // scalar field
- const char sfName[] = "Sensor visibility";
- int sfIdx = pointCloud->getScalarFieldIndexByName(sfName);
- if (sfIdx < 0)
- sfIdx = pointCloud->addScalarField(sfName);
- if (sfIdx < 0)
- {
- ccLog::Error(tr("Failed to allocate memory for output scalar field!"));
- return;
- }
- CCCoreLib::ScalarField* sf = pointCloud->getScalarField(sfIdx);
- assert(sf);
- if (sf)
- {
- sf->fill(0);
- //progress bar
- ccProgressDialog pdlg(true);
- CCCoreLib::NormalizedProgress nprogress(&pdlg,pointCloud->size());
- pdlg.setMethodTitle(tr("Compute visibility"));
- pdlg.setInfo(tr("Points: %L1").arg( pointCloud->size() ));
- pdlg.start();
- QApplication::processEvents();
- for (unsigned i = 0; i < pointCloud->size(); i++)
- {
- const CCVector3* P = pointCloud->getPoint(i);
- unsigned char visibility = sensor->checkVisibility(*P);
- ScalarType visValue = static_cast<ScalarType>(visibility);
- sf->setValue(i, visValue);
- if (!nprogress.oneStep())
- {
- //cancelled by user
- pointCloud->deleteScalarField(sfIdx);
- sf = nullptr;
- break;
- }
- }
- if (sf)
- {
- sf->computeMinAndMax();
- pointCloud->setCurrentDisplayedScalarField(sfIdx);
- pointCloud->showSF(true);
- ccConsole::Print(tr("Visibility computed for cloud '%1'").arg(pointCloud->getName()));
- ccConsole::Print(tr("\tVisible = %1").arg(CCCoreLib::POINT_VISIBLE));
- ccConsole::Print(tr("\tHidden = %1").arg(CCCoreLib::POINT_HIDDEN));
- ccConsole::Print(tr("\tOut of range = %1").arg(CCCoreLib::POINT_OUT_OF_RANGE));
- ccConsole::Print(tr("\tOut of fov = %1").arg(CCCoreLib::POINT_OUT_OF_FOV));
- }
- pointCloud->redrawDisplay();
- }
- updateUI();
- }
- void MainWindow::doActionConvertTextureToColor()
- {
- if ( !ccEntityAction::convertTextureToColor(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSamplePointsOnMesh()
- {
- static unsigned s_ptsSamplingCount = 1000000;
- static double s_ptsSamplingDensity = 10.0;
- static bool s_ptsSampleNormals = true;
- static bool s_useDensity = false;
- ccPtsSamplingDlg dlg(this);
- //restore last parameters
- dlg.setPointsNumber(s_ptsSamplingCount);
- dlg.setDensityValue(s_ptsSamplingDensity);
- dlg.setGenerateNormals(s_ptsSampleNormals);
- dlg.setUseDensity(s_useDensity);
- if (!dlg.exec())
- return;
- ccProgressDialog pDlg(false, this);
- pDlg.setAutoClose(false);
- bool withNormals = dlg.generateNormals();
- bool withRGB = dlg.interpolateRGB();
- bool withTexture = dlg.interpolateTexture();
- s_useDensity = dlg.useDensity();
- s_ptsSamplingCount = dlg.getPointsNumber();
- s_ptsSamplingDensity = dlg.getDensityValue();
- s_ptsSampleNormals = withNormals;
- bool errors = false;
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (!entity->isKindOf(CC_TYPES::MESH))
- continue;
-
- ccGenericMesh* mesh = ccHObjectCaster::ToGenericMesh(entity);
- assert(mesh);
-
- ccPointCloud* cloud = mesh->samplePoints( s_useDensity,
- s_useDensity ? s_ptsSamplingDensity : s_ptsSamplingCount,
- withNormals,
- withRGB,
- withTexture,
- &pDlg );
-
- if (cloud)
- {
- addToDB(cloud);
- }
- else
- {
- errors = true;
- }
- }
- if (errors)
- ccLog::Error(tr("[doActionSamplePointsOnMesh] Errors occurred during the process! Result may be incomplete!"));
- refreshAll();
- }
- void MainWindow::doActionSamplePointsOnPolyline()
- {
- static unsigned s_ptsSamplingCount = 1000;
- static double s_ptsSamplingDensity = 10.0;
- static bool s_useDensity = false;
- ccPtsSamplingDlg dlg(this);
- //restore last parameters
- dlg.setPointsNumber(s_ptsSamplingCount);
- dlg.setDensityValue(s_ptsSamplingDensity);
- dlg.setUseDensity(s_useDensity);
- dlg.optionsFrame->setVisible(false);
- if (!dlg.exec())
- return;
- s_ptsSamplingCount = dlg.getPointsNumber();
- s_ptsSamplingDensity = dlg.getDensityValue();
- s_useDensity = dlg.useDensity();
- bool errors = false;
- for (ccHObject *entity : getSelectedEntities())
- {
- if (!entity->isKindOf(CC_TYPES::POLY_LINE))
- continue;
- ccPolyline* poly = ccHObjectCaster::ToPolyline(entity);
- assert(poly);
- ccPointCloud* cloud = poly->samplePoints( s_useDensity,
- s_useDensity ? s_ptsSamplingDensity : s_ptsSamplingCount,
- true);
- if (cloud)
- {
- addToDB(cloud);
- }
- else
- {
- errors = true;
- }
- }
- if (errors)
- {
- ccLog::Error(tr("[DoActionSamplePointsOnPolyline] Errors occurred during the process! Result may be incomplete!"));
- }
- refreshAll();
- }
- void MainWindow::doActionSmoohPolyline()
- {
- static int s_iterationCount = 5;
- static double s_ratio = 0.25;
- ccSmoothPolylineDialog dlg(this);
- //restore last parameters
- dlg.setIerationCount(s_iterationCount);
- dlg.setRatio(s_ratio);
- if (!dlg.exec())
- return;
- s_iterationCount = dlg.getIerationCount();
- s_ratio = dlg.getRatio();
- bool errors = false;
- ccHObject::Container selectedEntities = getSelectedEntities();
- m_ccRoot->unselectAllEntities();
- for (ccHObject *entity : selectedEntities)
- {
- if (!entity->isKindOf(CC_TYPES::POLY_LINE))
- continue;
- ccPolyline* poly = ccHObjectCaster::ToPolyline(entity);
- assert(poly);
- ccPolyline* smoothPoly = poly->smoothChaikin(s_ratio, static_cast<unsigned>(s_iterationCount));
- if (smoothPoly)
- {
- if (poly->getParent())
- {
- poly->getParent()->addChild(smoothPoly);
- }
- poly->setEnabled(false);
- addToDB(smoothPoly);
- m_ccRoot->selectEntity(smoothPoly, true);
- }
- else
- {
- errors = true;
- }
- }
- if (errors)
- {
- ccLog::Error(tr("[DoActionSmoohPolyline] Errors occurred during the process! Result may be incomplete!"));
- }
- refreshAll();
- }
- void MainWindow::doRemoveDuplicatePoints()
- {
- if (!haveSelection())
- return;
- bool first = true;
- //persistent setting(s)
- QSettings settings;
- settings.beginGroup(ccPS::DuplicatePointsGroup());
- double minDistanceBetweenPoints = settings.value(ccPS::DuplicatePointsMinDist(),1.0e-12).toDouble();
- bool ok;
- minDistanceBetweenPoints = QInputDialog::getDouble(this, tr("Remove duplicate points"), tr("Min distance between points:"), minDistanceBetweenPoints, 0, 1.0e8, 12, &ok);
- if (!ok)
- return;
- //save parameter
- settings.setValue(ccPS::DuplicatePointsMinDist(), minDistanceBetweenPoints);
- ccProgressDialog pDlg(true, this);
- pDlg.setAutoClose(false);
- ccHObject::Container selectedEntities = getSelectedEntities(); //we have to use a local copy: 'unselectAllEntities' and 'selectEntity' will change the set of currently selected entities!
- for (ccHObject* entity : selectedEntities)
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- if (cloud)
- {
- ccPointCloud* filteredCloud = cloud->removeDuplicatePoints(minDistanceBetweenPoints, &pDlg);
- if (!filteredCloud)
- {
- ccConsole::Error(tr("Process failed (see Console)"));
- break;
- }
- if (filteredCloud != cloud) // otherwise the cloud has no duplicate point
- {
- filteredCloud->prepareDisplayForRefresh();
- addToDB(filteredCloud);
- if (first)
- {
- m_ccRoot->unselectAllEntities();
- first = false;
- }
- cloud->setEnabled(false);
- m_ccRoot->selectEntity(filteredCloud, true);
- }
- }
- }
- if (!first)
- {
- ccConsole::Warning(tr("Previously selected entities (sources) have been hidden!"));
- }
- refreshAll();
- }
- void MainWindow::doActionFilterByValue()
- {
- typedef std::pair<ccHObject*, ccPointCloud*> EntityAndVerticesType;
- std::vector<EntityAndVerticesType> toFilter;
-
- for ( ccHObject *entity : getSelectedEntities() )
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity);
- if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
- //la methode est activee sur le champ scalaire affiche
- CCCoreLib::ScalarField* sf = pc->getCurrentDisplayedScalarField();
- if (sf)
- {
- toFilter.emplace_back(entity,pc);
- }
- else
- {
- ccConsole::Warning(tr("Entity [%1] has no active scalar field!").arg(entity->getName()));
- }
- }
- }
- if (toFilter.empty())
- return;
-
- double minVald = 0.0;
- double maxVald = 1.0;
- //compute min and max "displayed" scalar values of currently selected
- //entities (the ones with an active scalar field only!)
- {
- for (size_t i = 0; i < toFilter.size(); ++i)
- {
- ccScalarField* sf = toFilter[i].second->getCurrentDisplayedScalarField();
- assert(sf);
- if (i == 0)
- {
- minVald = static_cast<double>(sf->displayRange().start());
- maxVald = static_cast<double>(sf->displayRange().stop());
- }
- else
- {
- if (minVald > static_cast<double>(sf->displayRange().start()))
- minVald = static_cast<double>(sf->displayRange().start());
- if (maxVald < static_cast<double>(sf->displayRange().stop()))
- maxVald = static_cast<double>(sf->displayRange().stop());
- }
- }
- }
- ccFilterByValueDlg dlg(minVald, maxVald, -1.0e9, 1.0e9, this);
- if (!dlg.exec())
- return;
- ccFilterByValueDlg::Mode mode = dlg.mode();
- assert(mode != ccFilterByValueDlg::CANCEL);
- ScalarType minVal = static_cast<ScalarType>(dlg.minDoubleSpinBox->value());
- ScalarType maxVal = static_cast<ScalarType>(dlg.maxDoubleSpinBox->value());
- ccHObject::Container results;
- {
- for ( auto &item : toFilter )
- {
- ccHObject* ent = item.first;
- ccPointCloud* pc = item.second;
- //we set as output (OUT) the currently displayed scalar field
- int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
- assert(outSfIdx >= 0);
- pc->setCurrentOutScalarField(outSfIdx);
- ccHObject* resultInside = nullptr;
- ccHObject* resultOutside = nullptr;
- if (ent->isKindOf(CC_TYPES::MESH))
- {
- pc->hidePointsByScalarValue(minVal, maxVal);
- if (ent->isA(CC_TYPES::MESH)/*|| ent->isKindOf(CC_TYPES::PRIMITIVE)*/) //TODO
- resultInside = ccHObjectCaster::ToMesh(ent)->createNewMeshFromSelection(false, nullptr, true);
- else if (ent->isA(CC_TYPES::SUB_MESH))
- resultInside = ccHObjectCaster::ToSubMesh(ent)->createNewSubMeshFromSelection(false);
- if (resultInside == ent)
- {
- //specific case: all triangles were selected, nothing to do
- ccLog::Warning(QString("Mesh %1 is fully inside the specified range").arg(ent->getName()));
- resultInside = nullptr;
- }
- else if (mode == ccFilterByValueDlg::SPLIT)
- {
- pc->invertVisibilityArray();
- if (ent->isA(CC_TYPES::MESH)/*|| ent->isKindOf(CC_TYPES::PRIMITIVE)*/) //TODO
- resultOutside = ccHObjectCaster::ToMesh(ent)->createNewMeshFromSelection(false, nullptr, true);
- else if (ent->isA(CC_TYPES::SUB_MESH))
- resultOutside = ccHObjectCaster::ToSubMesh(ent)->createNewSubMeshFromSelection(false);
- if (resultOutside == ent)
- {
- //specific case: all triangles were selected, nothing to do
- ccLog::Warning(QString("Mesh %1 is fully outside the specified range").arg(ent->getName()));
- ent->setEnabled(false);
- ent->prepareDisplayForRefresh();
- delete resultInside; // we don't need it
- resultInside = nullptr;
- resultOutside = nullptr;
- }
- }
- pc->unallocateVisibilityArray();
- }
- else if (ent->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- //shortcut, as we know here that the point cloud is a "ccPointCloud"
- resultInside = pc->filterPointsByScalarValue(minVal, maxVal, false);
-
- if (resultInside == ent)
- {
- //specific case: all points were selected, nothing to do
- ccLog::Warning(QString("Cloud %1 is fully inside the specified range").arg(ent->getName()));
- resultInside = nullptr;
- }
- else if (mode == ccFilterByValueDlg::SPLIT)
- {
- resultOutside = pc->filterPointsByScalarValue(minVal, maxVal, true);
- if (resultOutside == ent)
- {
- //specific case: all points were selected, nothing to do
- ccLog::Warning(QString("Cloud %1 is fully outside the specified range").arg(ent->getName()));
- ent->setEnabled(false);
- ent->prepareDisplayForRefresh();
- delete resultInside;
- resultInside = nullptr;
- resultOutside = nullptr;
- }
- }
- }
- if (resultInside)
- {
- ent->setEnabled(false);
- resultInside->setDisplay(ent->getDisplay());
- resultInside->prepareDisplayForRefresh();
- addToDB(resultInside);
- results.push_back(resultInside);
- }
- if (resultOutside)
- {
- ent->setEnabled(false);
- resultOutside->setDisplay(ent->getDisplay());
- resultOutside->prepareDisplayForRefresh();
- resultOutside->setName(resultOutside->getName() + ".outside");
- addToDB(resultOutside);
- results.push_back(resultOutside);
- }
- }
- }
- if (!results.empty())
- {
- ccConsole::Warning(tr("Previously selected entities (sources) have been hidden!"));
- if (m_ccRoot)
- {
- m_ccRoot->selectEntities(results);
- }
- }
- refreshAll();
- }
- void MainWindow::doActionSFConvertToRandomRGB()
- {
- if ( !ccEntityAction::sfConvertToRandomRGB(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSFConvertToRGB()
- {
- if ( !ccEntityAction::sfConvertToRGB(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionToggleActiveSFColorScale()
- {
- doApplyActiveSFAction(0);
- }
- void MainWindow::doActionShowActiveSFPrevious()
- {
- doApplyActiveSFAction(1);
- }
- void MainWindow::doActionShowActiveSFNext()
- {
- doApplyActiveSFAction(2);
- }
- void MainWindow::doApplyActiveSFAction(int action)
- {
- if (!haveOneSelection())
- {
- if (haveSelection())
- {
- ccConsole::Error(tr("Select only one cloud or one mesh!"));
- }
- return;
- }
- ccHObject* ent = m_selectedEntities.front();
- bool lockedVertices;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent,&lockedVertices);
- //for "real" point clouds only
- if (!cloud)
- return;
- if (lockedVertices && !ent->isAncestorOf(cloud))
- {
- //see ccPropertiesTreeDelegate::fillWithMesh
- ccUtils::DisplayLockedVerticesWarning(ent->getName(),true);
- return;
- }
- assert(cloud);
- int sfIdx = cloud->getCurrentDisplayedScalarFieldIndex();
- switch (action)
- {
- case 0: //Toggle SF color scale
- if (sfIdx >= 0)
- {
- cloud->showSFColorsScale(!cloud->sfColorScaleShown());
- cloud->prepareDisplayForRefresh();
- }
- else
- ccConsole::Warning(tr("No active scalar field on entity '%1'").arg(ent->getName()));
- break;
- case 1: //Activate previous SF
- if (sfIdx >= 0)
- {
- cloud->setCurrentDisplayedScalarField(sfIdx-1);
- cloud->prepareDisplayForRefresh();
- }
- break;
- case 2: //Activate next SF
- if (sfIdx+1 < static_cast<int>(cloud->getNumberOfScalarFields()))
- {
- cloud->setCurrentDisplayedScalarField(sfIdx+1);
- cloud->prepareDisplayForRefresh();
- }
- break;
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionRenameSF()
- {
- if ( !ccEntityAction::sfRename(m_selectedEntities, this) )
- return;
- updateUI();
- }
- void MainWindow::doActionOpenColorScalesManager()
- {
- ccColorScaleEditorDialog cseDlg(ccColorScalesManager::GetUniqueInstance(), this, ccColorScale::Shared(nullptr), this);
- if (cseDlg.exec())
- {
- //save current scale manager state to persistent settings
- ccColorScalesManager::GetUniqueInstance()->toPersistentSettings();
- }
- updateUI();
- }
- void MainWindow::doActionAddIdField()
- {
- if (!ccEntityAction::sfAddIdField(m_selectedEntities))
- {
- return;
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSplitCloudUsingSF()
- {
- if (!ccEntityAction::sfSplitCloud(m_selectedEntities, this))
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionRGBGaussianFilter()
- {
- ccPointCloud::RgbFilterOptions(filterParams);
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN;
- if (!ccEntityAction::rgbGaussianFilter(m_selectedEntities, filterParams, this))
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionRGBBilateralFilter()
- {
- ccPointCloud::RgbFilterOptions(filterParams);
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::BILATERAL;
- if (!ccEntityAction::rgbGaussianFilter(m_selectedEntities, filterParams, this))
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionRGBMeanFilter()
- {
- ccPointCloud::RgbFilterOptions(filterParams);
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::MEAN;
- if (!ccEntityAction::rgbGaussianFilter(m_selectedEntities, filterParams, this))
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionRGBMedianFilter()
- {
- ccPointCloud::RgbFilterOptions(filterParams);
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::MEDIAN;
- if (!ccEntityAction::rgbGaussianFilter(m_selectedEntities, filterParams, this))
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSFGaussianFilter()
- {
- ccPointCloud::RgbFilterOptions(filterParams);
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN;
- if ( !ccEntityAction::sfGaussianFilter(m_selectedEntities, filterParams, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSFBilateralFilter()
- {
- ccPointCloud::RgbFilterOptions(filterParams);
- filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::BILATERAL;
- if ( !ccEntityAction::sfGaussianFilter(m_selectedEntities, filterParams, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSmoothMeshSF()
- {
- if ( !ccEntityAction::processMeshSF(m_selectedEntities, ccMesh::SMOOTH_MESH_SF, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionEnhanceMeshSF()
- {
- if ( !ccEntityAction::processMeshSF(m_selectedEntities, ccMesh::ENHANCE_MESH_SF, this) )
- return;
- refreshAll();
- updateUI();
- }
- static double s_subdivideMaxArea = 1.0;
- void MainWindow::doActionSubdivideMesh()
- {
- bool ok;
- s_subdivideMaxArea = QInputDialog::getDouble(this, tr("Subdivide mesh"), tr("Max area per triangle:"), s_subdivideMaxArea, 1e-6, 1e6, 8, &ok);
- if (!ok)
- return;
- //ccProgressDialog pDlg(true, this);
- //pDlg.setAutoClose(false);
- bool warningIssued = false;
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isKindOf(CC_TYPES::MESH))
- {
- //single mesh?
- if (entity->isA(CC_TYPES::MESH))
- {
- ccMesh* mesh = static_cast<ccMesh*>(entity);
- ccMesh* subdividedMesh = nullptr;
- try
- {
- subdividedMesh = mesh->subdivide(static_cast<PointCoordinateType>(s_subdivideMaxArea));
- }
- catch(...)
- {
- ccLog::Error(tr("[Subdivide] An error occurred while trying to subdivide mesh '%1' (not enough memory?)").arg(mesh->getName()));
- }
- if (subdividedMesh)
- {
- subdividedMesh->setName(QString("%1.subdivided(S<%2)").arg(mesh->getName()).arg(s_subdivideMaxArea));
- subdividedMesh->setDisplay(mesh->getDisplay());
- mesh->redrawDisplay();
- mesh->setEnabled(false);
- addToDB(subdividedMesh);
- }
- else
- {
- ccConsole::Warning(tr("[Subdivide] Failed to subdivide mesh '%1' (not enough memory?)").arg(mesh->getName()));
- }
- }
- else if (!warningIssued)
- {
- ccLog::Warning(tr("[Subdivide] Works only on real meshes!"));
- warningIssued = true;
- }
- }
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionFlipMeshTriangles()
- {
- bool warningIssued = false;
- for (ccHObject *entity : getSelectedEntities())
- {
- if (entity->isKindOf(CC_TYPES::MESH))
- {
- //single mesh?
- if (entity->isA(CC_TYPES::MESH))
- {
- ccMesh* mesh = static_cast<ccMesh*>(entity);
- mesh->flipTriangles();
- mesh->prepareDisplayForRefresh();
- }
- else if (!warningIssued)
- {
- ccLog::Warning(tr("[Flip triangles] Works only on real meshes!"));
- warningIssued = true;
- }
- }
- }
- refreshAll();
- }
- void MainWindow::doActionSmoothMeshLaplacian()
- {
- static unsigned s_laplacianSmooth_nbIter = 20;
- static double s_laplacianSmooth_factor = 0.2;
- bool ok;
- s_laplacianSmooth_nbIter = QInputDialog::getInt(this, tr("Smooth mesh"), tr("Iterations:"), s_laplacianSmooth_nbIter, 1, 1000, 1, &ok);
- if (!ok)
- return;
- s_laplacianSmooth_factor = QInputDialog::getDouble(this, tr("Smooth mesh"), tr("Smoothing factor:"), s_laplacianSmooth_factor, 0, 100, 3, &ok);
- if (!ok)
- return;
- ccProgressDialog pDlg(true, this);
- pDlg.setAutoClose(false);
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isA(CC_TYPES::MESH) || entity->isA(CC_TYPES::PRIMITIVE)) //FIXME: can we really do this with primitives?
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(entity);
- if (mesh->laplacianSmooth( s_laplacianSmooth_nbIter,
- static_cast<PointCoordinateType>(s_laplacianSmooth_factor),
- &pDlg) )
- {
- mesh->prepareDisplayForRefresh_recursive();
- }
- else
- {
- ccConsole::Warning(tr("Failed to apply Laplacian smoothing to mesh '%1'").arg(mesh->getName()));
- }
- }
- }
- refreshAll();
- updateUI();
- }
- // helper for doActionMerge
- void AddToRemoveList(ccHObject* toRemove, ccHObject::Container& toBeRemovedList)
- {
- // is a parent or sibling already in the "toBeRemoved" list?
- size_t count = toBeRemovedList.size();
- for (size_t j = 0; j < count;)
- {
- if (toBeRemovedList[j]->isAncestorOf(toRemove))
- {
- // nothing to do, we already have an ancestor
- return;
- }
- else if (toRemove->isAncestorOf(toBeRemovedList[j]))
- {
- // we don't need to keep the children
- toBeRemovedList[j] = toBeRemovedList.back();
- toBeRemovedList.pop_back();
- count--;
- }
- else
- {
- // forward
- ++j;
- }
- }
- toBeRemovedList.push_back(toRemove);
- }
- void MainWindow::doActionMerge()
- {
- //let's look for clouds or meshes (warning: we don't mix them)
- std::vector<ccPointCloud*> clouds;
- std::vector<ccMesh*> meshes;
- try
- {
- for (ccHObject* entity : getSelectedEntities())
- {
- if (!entity)
- {
- continue;
- }
- if (entity->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- clouds.push_back(cloud);
- // check whether this cloud is an ancestor of the first cloud in the selection
- if (clouds.size() > 1)
- {
- if (clouds.back()->isAncestorOf(clouds.front()))
- {
- // this way we are sure that the first cloud is not below any other cloud
- std::swap(clouds.front(), clouds.back());
- }
- }
- }
- else if (entity->isKindOf(CC_TYPES::MESH))
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(entity);
- //this is a purely theoretical test for now!
- if (mesh && mesh->getAssociatedCloud() && mesh->getAssociatedCloud()->isA(CC_TYPES::POINT_CLOUD))
- {
- meshes.push_back(mesh);
- }
- else
- {
- ccConsole::Warning(tr("Only meshes with standard vertices are handled for now! Can't merge entity '%1'...").arg(entity->getName()));
- }
- }
- else
- {
- ccConsole::Warning(tr("Entity '%1' is neither a cloud nor a mesh, can't merge it!").arg(entity->getName()));
- }
- }
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Error(tr("Not enough memory!"));
- return;
- }
- if (clouds.empty() && meshes.empty())
- {
- ccLog::Error(tr("Select only clouds or meshes!"));
- return;
- }
- if (!clouds.empty() && !meshes.empty())
- {
- ccLog::Error(tr("Can't mix point clouds and meshes!"));
- }
- //merge clouds?
- if (!clouds.empty())
- {
- //we deselect all selected entities (as most of them are going to disappear)
- if (m_ccRoot)
- {
- m_ccRoot->unselectAllEntities();
- assert(!haveSelection());
- //m_selectedEntities.clear();
- }
- //we will remove the useless clouds/meshes later
- ccHObject::Container toBeRemoved;
- ccPointCloud* firstCloud = nullptr;
- ccHObjectContext firstCloudContext;
- //whether to generate the 'original cloud index' scalar field or not
- CCCoreLib::ScalarField* ocIndexSF = nullptr;
- size_t cloudIndex = 0;
- //compute total size of the final cloud
- unsigned totalSize = 0;
- for (size_t i = 0; i < clouds.size(); ++i)
- {
- totalSize += clouds[i]->size();
- }
- for (size_t i = 0; i < clouds.size(); ++i)
- {
- ccPointCloud* pc = clouds[i];
- if (!firstCloud)
- {
- //we don't delete the first cloud (we'll merge the other one 'inside' it
- firstCloud = pc;
- //we still have to temporarily detach the first cloud, as it may undergo
- //'severe' modifications (octree deletion, etc.) --> see ccPointCloud::operator +=
- firstCloudContext = removeObjectTemporarilyFromDBTree(firstCloud);
- //reserve the final required number of points
- if (!firstCloud->reserve(totalSize))
- {
- ccConsole::Error(tr("Not enough memory!"));
- break;
- }
- if (QMessageBox::question(this, tr("Original cloud index"), tr("Do you want to generate a scalar field with the original cloud index?")) == QMessageBox::Yes)
- {
- int sfIdx = pc->getScalarFieldIndexByName(CC_ORIGINAL_CLOUD_INDEX_SF_NAME);
- if (sfIdx < 0)
- {
- sfIdx = pc->addScalarField(CC_ORIGINAL_CLOUD_INDEX_SF_NAME);
- }
- if (sfIdx < 0)
- {
- ccConsole::Error(tr("Couldn't allocate a new scalar field for storing the original cloud index! Try to free some memory ..."));
- return;
- }
- else
- {
- ocIndexSF = pc->getScalarField(sfIdx);
- if (ocIndexSF)
- {
- ocIndexSF->fill(0);
- firstCloud->setCurrentDisplayedScalarField(sfIdx);
- }
- }
- }
- }
- else
- {
- unsigned countBefore = firstCloud->size();
- unsigned countAdded = pc->size();
- firstCloud->append(pc, countBefore, false, false); //append without recalculating SF min/max
- //success?
- if (firstCloud->size() == countBefore + countAdded)
- {
- firstCloud->prepareDisplayForRefresh_recursive();
- ccHObject* toRemove = nullptr;
- //if the entity to remove is inside a group with a unique child, we can remove the group as well
- ccHObject* parent = pc->getParent();
- if (parent && parent->isA(CC_TYPES::HIERARCHY_OBJECT) && parent->getChildrenNumber() == 1 && parent != firstCloudContext.parent)
- toRemove = parent;
- else
- toRemove = pc;
- AddToRemoveList(toRemove, toBeRemoved);
- if (ocIndexSF)
- {
- ScalarType index = static_cast<ScalarType>(++cloudIndex);
- for (unsigned i = 0; i < countAdded; ++i)
- {
- ocIndexSF->setValue(countBefore + i, index);
- }
- }
- }
- else
- {
- ccConsole::Error(tr("Fusion failed! (not enough memory?)"));
- break;
- }
- pc = nullptr;
- }
- }
- //compute min and max once after all appends are done
- if (firstCloud)
- {
- for (unsigned i = 0; i < firstCloud->getNumberOfScalarFields(); i++)
- {
- firstCloud->getScalarField(i)->computeMinAndMax();
- }
- if (ocIndexSF)
- {
- firstCloud->showSF(true);
- }
- }
- //something to remove?
- for (ccHObject* toRemove : toBeRemoved)
- {
- if (firstCloud->isAncestorOf(toRemove))
- {
- // we cannot call 'removeElement' on a child of the first cloud, as it's temporarily detached from the DB tree!
- if (toRemove->getParent())
- toRemove->getParent()->removeChild(toRemove);
- else
- delete toRemove;
- }
- else
- {
- m_ccRoot->removeElement(toRemove);
- }
- }
- toBeRemoved.clear();
- //eventually we can put back the first cloud in DB
- if (firstCloud)
- {
- putObjectBackIntoDBTree(firstCloud, firstCloudContext);
- if (m_ccRoot)
- m_ccRoot->selectEntity(firstCloud);
- }
- }
- //merge meshes?
- else if (!meshes.empty())
- {
- bool createSubMeshes = true;
- //createSubMeshes = (QMessageBox::question(this, tr("Create sub-meshes"), tr("Do you want to create sub-mesh entities corresponding to each source mesh? (requires more memory)"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes);
- //meshes are merged
- ccPointCloud* baseVertices = new ccPointCloud("vertices");
- ccMesh* baseMesh = new ccMesh(baseVertices);
- baseMesh->setName("Merged mesh");
- baseMesh->addChild(baseVertices);
- baseVertices->setEnabled(false);
- for (ccMesh *mesh : meshes)
- {
- //if (mesh->isA(CC_TYPES::PRIMITIVE))
- //{
- // mesh = mesh->ccMesh::cloneMesh(); //we want a clone of the mesh part, not the primitive!
- //}
- if (!baseMesh->merge(mesh, createSubMeshes))
- {
- ccConsole::Error(tr("Fusion failed! (not enough memory?)"));
- break;
- }
- }
- baseMesh->setDisplay_recursive(meshes.front()->getDisplay());
- baseMesh->setVisible(true);
- addToDB(baseMesh);
- if (m_ccRoot)
- m_ccRoot->selectEntity(baseMesh);
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::zoomOn(ccHObject* object)
- {
- ccGLWindowInterface* win = static_cast<ccGLWindowInterface*>(object->getDisplay());
- if (win)
- {
- ccBBox box = object->getDisplayBB_recursive(false,win);
- win->updateConstellationCenterAndZoom(&box);
- }
- }
- void MainWindow::doActionRegister()
- {
- if ( m_selectedEntities.size() != 2
- || (!m_selectedEntities.front()->isKindOf(CC_TYPES::POINT_CLOUD) && !m_selectedEntities.front()->isKindOf(CC_TYPES::MESH))
- || (!m_selectedEntities.back()->isKindOf(CC_TYPES::POINT_CLOUD) && !m_selectedEntities.back()->isKindOf(CC_TYPES::MESH)) )
- {
- ccConsole::Error(tr("Select 2 point clouds or meshes!"));
- return;
- }
- ccHObject* data = static_cast<ccHObject*>(m_selectedEntities.front());
- ccHObject* model = static_cast<ccHObject*>(m_selectedEntities.back());
- if (data->isKindOf(CC_TYPES::MESH) && model->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- //by default, prefer the mesh as the reference
- std::swap(data, model);
- }
- ccRegistrationDlg rDlg(data, model, this);
- if (!rDlg.exec())
- return;
- //model and data order may have changed!
- model = rDlg.getModelEntity();
- data = rDlg.getDataEntity();
- double minRMSDecrease = rDlg.getMinRMSDecrease();
- if (std::isnan(minRMSDecrease))
- {
- ccLog::Error(tr("Invalid minimum RMS decrease value"));
- return;
- }
- if (minRMSDecrease < ccRegistrationDlg::GetAbsoluteMinRMSDecrease())
- {
- minRMSDecrease = ccRegistrationDlg::GetAbsoluteMinRMSDecrease();
- ccLog::Error(tr("Minimum RMS decrease value is too small.\n%1 will be used instead (numerical accuracy limit).").arg(minRMSDecrease, 0, 'E', 1));
- rDlg.setMinRMSDecrease(minRMSDecrease);
- }
- CCCoreLib::ICPRegistrationTools::Parameters parameters;
- {
- parameters.convType = rDlg.getConvergenceMethod();
- parameters.minRMSDecrease = minRMSDecrease;
- parameters.nbMaxIterations = rDlg.getMaxIterationCount();
- parameters.adjustScale = rDlg.adjustScale();
- parameters.filterOutFarthestPoints = rDlg.removeFarthestPoints();
- parameters.samplingLimit = rDlg.randomSamplingLimit();
- parameters.finalOverlapRatio = rDlg.getFinalOverlap() / 100.0;
- parameters.transformationFilters = rDlg.getTransformationFilters();
- parameters.maxThreadCount = rDlg.getMaxThreadCount();
- parameters.useC2MSignedDistances = rDlg.useC2MSignedDistances(parameters.robustC2MSignedDistances);
- parameters.normalsMatching = rDlg.normalsMatchingOption();
- }
- bool useDataSFAsWeights = rDlg.useDataSFAsWeights();
- bool useModelSFAsWeights = rDlg.useModelSFAsWeights();
- //semi-persistent storage (for next call)
- rDlg.saveParameters();
- ccGLMatrix transMat;
- double finalError = 0.0;
- double finalScale = 1.0;
- unsigned finalPointCount = 0;
- if (ccRegistrationTools::ICP( data,
- model,
- transMat,
- finalScale,
- finalError,
- finalPointCount,
- parameters,
- useDataSFAsWeights,
- useModelSFAsWeights,
- this))
- {
- QString rmsString = tr("Final RMS*: %1 (computed on %2 points)").arg(finalError).arg(finalPointCount);
- QString rmsDisclaimerString = tr("(* RMS is potentially weighted, depending on the selected options)");
- ccLog::Print(QString("[Register] ") + rmsString);
- ccLog::Print(QString("[Register] ") + rmsDisclaimerString);
- QStringList summary;
- summary << rmsString;
- summary << rmsDisclaimerString;
- summary << "----------------";
- //transformation matrix
- {
- summary << "Transformation matrix";
- summary << transMat.toString(3, '\t'); //low precision, just for display
- summary << "----------------";
- ccLog::Print(tr("[Register] Applied transformation matrix:"));
- ccLog::Print(transMat.toString(12, ' ')); //full precision
- ccLog::Print(tr("Hint: copy it (CTRL+C) and apply it - or its inverse - on any entity with the 'Edit > Apply transformation' tool"));
- }
- if (parameters.adjustScale)
- {
- QString scaleString = tr("Scale: %1 (already integrated in above matrix!)").arg(finalScale);
- ccLog::Warning(QString("[Register] ") + scaleString);
- summary << scaleString;
- }
- else
- {
- ccLog::Print(tr("[Register] Scale: fixed (1.0)"));
- summary << tr("Scale: fixed (1.0)");
- }
- //overlap
- summary << "----------------";
- QString overlapString = tr("Theoretical overlap: %1%").arg(static_cast<int>(parameters.finalOverlapRatio * 100));
- ccLog::Print(QString("[Register] ") + overlapString);
- summary << overlapString;
- summary << "----------------";
- summary << tr("This report has been output to Console (F8)");
- //cloud to move
- ccGenericPointCloud* pc = nullptr;
- if (data->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- pc = ccHObjectCaster::ToGenericPointCloud(data);
- }
- else if (data->isKindOf(CC_TYPES::MESH))
- {
- ccGenericMesh* mesh = ccHObjectCaster::ToGenericMesh(data);
- pc = mesh->getAssociatedCloud();
- //warning: point cloud is locked!
- if (pc->isLocked())
- {
- pc = nullptr;
- //we ask the user about cloning the 'data' mesh
- QMessageBox::StandardButton result = QMessageBox::question( this,
- tr("Registration"),
- tr("Data mesh vertices are locked (they may be shared with other meshes): Do you wish to clone this mesh to apply transformation?"),
- QMessageBox::Ok | QMessageBox::Cancel,
- QMessageBox::Ok);
- //continue process?
- if (result == QMessageBox::Ok)
- {
- ccGenericMesh* newMesh = nullptr;
- if (mesh->isA(CC_TYPES::MESH))
- newMesh = static_cast<ccMesh*>(mesh)->cloneMesh();
- else
- {
- //FIXME TODO
- ccLog::Error(tr("Doesn't work on sub-meshes yet!"));
- }
- if (newMesh)
- {
- newMesh->setDisplay(data->getDisplay());
- addToDB(newMesh);
- data = newMesh;
- pc = newMesh->getAssociatedCloud();
- }
- else
- {
- ccLog::Error(tr("Failed to clone 'data' mesh! (not enough memory?)"));
- }
- }
- }
- }
- //if we managed to get a point cloud to move!
- if (pc)
- {
- //we temporarily detach cloud, as it may undergo
- //'severe' modifications (octree deletion, etc.) --> see ccPointCloud::applyRigidTransformation
- ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(pc);
- // check if the 'model' entity is a child of the 'data' one (would be strange, but can still happen!)
- bool modelIsChildOfData = data->isAncestorOf(model);
- if (modelIsChildOfData)
- {
- if (pc->getChildrenNumber() != 0)
- {
- ccLog::Warning(tr("[ICP] The reference entity is a child of the aligned one! CC will move only the aligned entity, and not its children"));
- }
- pc->applyRigidTransformation(transMat);
- }
- else
- {
- // we can safely apply the transformation to all the children
- pc->applyGLTransformation_recursive(&transMat);
- }
- putObjectBackIntoDBTree(pc, objContext);
- //don't forget to update mesh bounding box also!
- if (data->isKindOf(CC_TYPES::MESH))
- {
- ccHObjectCaster::ToGenericMesh(data)->refreshBB();
- }
- //don't forget global shift
- ccGenericPointCloud* refPC = ccHObjectCaster::ToGenericPointCloud(model);
- if (refPC)
- {
- if (refPC->isShifted())
- {
- const CCVector3d& Pshift = refPC->getGlobalShift();
- double scale = refPC->getGlobalScale();
- pc->setGlobalShift(Pshift);
- pc->setGlobalScale(scale);
- ccLog::Warning(tr("[ICP] Aligned entity global shift has been updated to match the reference: (%1,%2,%3) [x%4]").arg(Pshift.x).arg(Pshift.y).arg(Pshift.z).arg(scale));
- ccGLMatrixd transMatD(transMat.data());
- transMatD.scale(1.0 / scale);
- transMatD.setTranslation(transMatD.getTranslationAsVec3D() - Pshift);
- ccLog::Print("[ICP] Transformation to global coordinates:");
- ccLog::Print(transMatD.toString(12, ' ')); //full precision
- }
- else if (pc->isShifted()) //we'll ask the user first before dropping the shift information on the aligned cloud
- {
- if (QMessageBox::question(this, tr("Drop shift information?"), tr("Aligned entity is shifted but reference cloud is not: drop global shift information?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
- {
- pc->setGlobalShift(0, 0, 0);
- pc->setGlobalScale(1.0);
- ccLog::Warning(tr("[ICP] Aligned entity global shift has been reset to match the reference!"));
- }
- }
- }
- data->prepareDisplayForRefresh_recursive();
- data->setName(data->getName() + QString(".registered"));
- zoomOn(data);
- }
- //pop-up summary
- QMessageBox::information(this, tr("Registration info"), summary.join("\n"));
- forceConsoleDisplay();
- }
- refreshAll();
- updateUI();
- }
- //Aurelien BEY le 13/11/2008 : ajout de la fonction permettant de traiter la fonctionnalite de recalage grossier
- void MainWindow::doAction4pcsRegister()
- {
- if (QMessageBox::warning( this,
- tr("Work in progress"),
- tr("This method is still under development: are you sure you want to use it? (a crash may likely happen)"),
- QMessageBox::Yes,QMessageBox::No) == QMessageBox::No )
- return;
- if (m_selectedEntities.size() != 2)
- {
- ccConsole::Error(tr("Select 2 point clouds!"));
- return;
- }
- if (!m_selectedEntities.front()->isKindOf(CC_TYPES::POINT_CLOUD) ||
- !m_selectedEntities.back()->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccConsole::Error(tr("Select 2 point clouds!"));
- return;
- }
- ccGenericPointCloud* data = ccHObjectCaster::ToGenericPointCloud(m_selectedEntities.back());
- ccGenericPointCloud* model = ccHObjectCaster::ToGenericPointCloud(m_selectedEntities.front());
- ccAlignDlg aDlg(data, model);
- if (!aDlg.exec())
- {
- return;
- }
- // model = aDlg.getModelObject();
- data = aDlg.getDataObject();
- //Take the correct number of points among the clouds
- CCCoreLib::ReferenceCloud* subModel = aDlg.getSampledModel();
- CCCoreLib::ReferenceCloud* subData = aDlg.getSampledData();
- if (!subModel || !subData)
- {
- delete subModel;
- delete subData;
- assert(false);
- return;
- }
- unsigned nbMaxCandidates = aDlg.isNumberOfCandidatesLimited() ? aDlg.getMaxNumberOfCandidates() : 0;
- ccProgressDialog pDlg(true, this);
- CCCoreLib::PointProjectionTools::Transformation transform;
- if (CCCoreLib::FPCSRegistrationTools::RegisterClouds( subModel,
- subData,
- transform,
- static_cast<ScalarType>(aDlg.getDelta()),
- static_cast<ScalarType>(aDlg.getDelta()/2),
- static_cast<PointCoordinateType>(aDlg.getOverlap()),
- aDlg.getNbTries(),
- 5000,
- &pDlg,
- nbMaxCandidates))
- {
- //output resulting transformation matrix
- {
- ccGLMatrix transMat = FromCCLibMatrix<double, float>(transform.R, transform.T);
- forceConsoleDisplay();
- ccConsole::Print(tr("[Align] Resulting matrix:"));
- ccConsole::Print(transMat.toString(12, ' ')); //full precision
- ccConsole::Print(tr("Hint: copy it (CTRL+C) and apply it - or its inverse - on any entity with the 'Edit > Apply transformation' tool"));
- }
- ccPointCloud* newDataCloud = data->isA(CC_TYPES::POINT_CLOUD) ? static_cast<ccPointCloud*>(data)->cloneThis() : ccPointCloud::From(data, data);
- if (data->getParent())
- {
- data->getParent()->addChild(newDataCloud);
- }
- newDataCloud->setName(data->getName() + QString(".registered"));
- transform.apply(*newDataCloud);
- newDataCloud->invalidateBoundingBox(); //invalidate bb
- newDataCloud->setDisplay(data->getDisplay());
- newDataCloud->prepareDisplayForRefresh();
- zoomOn(newDataCloud);
- addToDB(newDataCloud);
- data->setEnabled(false);
- data->prepareDisplayForRefresh_recursive();
- }
- else
- {
- ccConsole::Warning(tr("[Align] Registration failed!"));
- }
- delete subModel;
- subModel = nullptr;
- delete subData;
- subData = nullptr;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSubsample()
- {
- //find candidates
- std::vector<ccPointCloud*> clouds;
- unsigned maxPointCount = 0;
- double maxCloudRadius = 0;
- ScalarType sfMin = CCCoreLib::NAN_VALUE;
- ScalarType sfMax = CCCoreLib::NAN_VALUE;
- {
- for ( ccHObject* entity : getSelectedEntities() )
- {
- if (entity->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
- clouds.push_back(cloud);
- maxPointCount = std::max<unsigned>(maxPointCount, cloud->size());
- maxCloudRadius = std::max<double>(maxCloudRadius, cloud->getOwnBB().getDiagNorm());
- //we also look for the min and max sf values
- ccScalarField* sf = cloud->getCurrentDisplayedScalarField();
- if (sf)
- {
- if (!ccScalarField::ValidValue(sfMin) || sfMin > sf->getMin())
- sfMin = sf->getMin();
- if (!ccScalarField::ValidValue(sfMax) || sfMax < sf->getMax())
- sfMax = sf->getMax();
- }
- }
- }
- }
- if (clouds.empty())
- {
- ccConsole::Error(tr("Select at least one point cloud!"));
- return;
- }
- //Display dialog
- ccSubsamplingDlg sDlg(maxPointCount, maxCloudRadius, this);
- sDlg.loadFromPersistentSettings();
- bool hasValidSF = ccScalarField::ValidValue(sfMin) && ccScalarField::ValidValue(sfMax);
- if (hasValidSF)
- {
- sDlg.enableSFModulation(sfMin, sfMax);
- }
- if (!sDlg.exec())
- {
- return;
- }
- sDlg.saveToPersistentSettings();
- //process clouds
- ccHObject::Container resultingClouds;
- {
- ccProgressDialog pDlg(false, this);
- pDlg.setAutoClose(false);
- pDlg.setMethodTitle(tr("Subsampling"));
- bool errors = false;
- QElapsedTimer eTimer;
- eTimer.start();
- for (size_t i = 0; i < clouds.size(); ++i)
- {
- ccPointCloud* cloud = clouds[i];
- CCCoreLib::ReferenceCloud* sampledCloud = sDlg.getSampledCloud(cloud,&pDlg);
- if (!sampledCloud)
- {
- ccConsole::Warning(tr("[Subsampling] Failed to subsample cloud '%1'!").arg(cloud->getName()));
- errors = true;
- continue;
- }
- int warnings = 0;
- ccPointCloud* newPointCloud = cloud->partialClone(sampledCloud, &warnings);
- delete sampledCloud;
- sampledCloud = nullptr;
- if (newPointCloud)
- {
- newPointCloud->setName(cloud->getName() + QString(".subsampled"));
- newPointCloud->copyGlobalShiftAndScale(*cloud);
- newPointCloud->setDisplay(cloud->getDisplay());
- newPointCloud->prepareDisplayForRefresh();
- if (cloud->getParent())
- cloud->getParent()->addChild(newPointCloud);
- cloud->setEnabled(false);
- addToDB(newPointCloud);
- newPointCloud->prepareDisplayForRefresh();
- resultingClouds.push_back(newPointCloud);
- if (warnings)
- {
- ccLog::Warning(tr("[Subsampling] Not enough memory: colors, normals or scalar fields may be missing!"));
- errors = true;
- }
- }
- else
- {
- ccLog::Error(tr("Not enough memory!"));
- break;
- }
- }
- ccLog::Print("[Subsampling] Timing: %3.3f s.",eTimer.elapsed()/1000.0);
- if (errors)
- {
- ccLog::Error(tr("Errors occurred (see console)"));
- }
- }
- if (m_ccRoot)
- m_ccRoot->selectEntities(resultingClouds);
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionStatisticalTest()
- {
- if ( !ccEntityAction::statisticalTest(m_selectedEntities, this ) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionComputeStatParams()
- {
- ccEntityAction::computeStatParams(m_selectedEntities, this );
- }
- struct ComponentIndexAndSize
- {
- unsigned index;
- unsigned size;
- ComponentIndexAndSize(unsigned i, unsigned s) : index(i), size(s) {}
- static bool DescendingCompOperator(const ComponentIndexAndSize& a, const ComponentIndexAndSize& b)
- {
- return a.size > b.size;
- }
- };
- void MainWindow::createComponentsClouds(ccGenericPointCloud* cloud,
- CCCoreLib::ReferenceCloudContainer& components,
- unsigned minPointsPerComponent,
- bool randomColors,
- bool selectComponents,
- bool sortBysize/*=true*/)
- {
- if (!cloud || components.empty())
- return;
- std::vector<ComponentIndexAndSize> sortedIndexes;
- std::vector<ComponentIndexAndSize>* _sortedIndexes = nullptr;
- if (sortBysize)
- {
- try
- {
- sortedIndexes.reserve(components.size());
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Warning(tr("[CreateComponentsClouds] Not enough memory to sort components by size!"));
- sortBysize = false;
- }
- if (sortBysize) //still ok?
- {
- unsigned compCount = static_cast<unsigned>(components.size());
- for (unsigned i = 0; i < compCount; ++i)
- {
- sortedIndexes.emplace_back(i, components[i]->size());
- }
- ParallelSort(sortedIndexes.begin(), sortedIndexes.end(), ComponentIndexAndSize::DescendingCompOperator);
-
- _sortedIndexes = &sortedIndexes;
- }
- }
- //we create "real" point clouds for all input components
- {
- ccPointCloud* pc = cloud->isA(CC_TYPES::POINT_CLOUD) ? static_cast<ccPointCloud*>(cloud) : nullptr;
- //we create a new group to store all CCs
- ccHObject* ccGroup = new ccHObject(cloud->getName() + QString(" [CCs]"));
- //for each component
- for (size_t i = 0; i < components.size(); ++i)
- {
- CCCoreLib::ReferenceCloud* compIndexes = _sortedIndexes ? components[_sortedIndexes->at(i).index] : components[i];
- //if it has enough points
- if (compIndexes->size() >= minPointsPerComponent)
- {
- //we create a new entity
- ccPointCloud* compCloud = (pc ? pc->partialClone(compIndexes) : ccPointCloud::From(compIndexes));
- if (compCloud)
- {
- //shall we colorize it with random color?
- if (randomColors)
- {
- ccColor::Rgb col = ccColor::Generator::Random();
- compCloud->setColor(col);
- compCloud->showColors(true);
- compCloud->showSF(false);
- }
- //'shift on load' information
- if (pc)
- {
- compCloud->copyGlobalShiftAndScale(*pc);
- }
- compCloud->setVisible(true);
- compCloud->setName(QString("CC#%1").arg(ccGroup->getChildrenNumber()));
- //we add new CC to group
- ccGroup->addChild(compCloud);
- if (selectComponents && m_ccRoot)
- m_ccRoot->selectEntity(compCloud, true);
- }
- else
- {
- ccConsole::Warning(tr("[CreateComponentsClouds] Failed to create component #%1! (not enough memory)").arg(ccGroup->getChildrenNumber() + 1));
- }
- }
- delete compIndexes;
- compIndexes = nullptr;
- }
- components.clear();
- if (ccGroup->getChildrenNumber() == 0)
- {
- ccConsole::Error(tr("No component was created! Check the minimum size..."));
- delete ccGroup;
- ccGroup = nullptr;
- }
- else
- {
- ccGroup->setDisplay(cloud->getDisplay());
- addToDB(ccGroup);
- ccConsole::Print(tr("[CreateComponentsClouds] %1 component(s) were created from cloud '%2'").arg(ccGroup->getChildrenNumber()).arg(cloud->getName()));
- }
- cloud->prepareDisplayForRefresh();
- //auto-hide original cloud
- if (ccGroup)
- {
- cloud->setEnabled(false);
- ccConsole::Warning(tr("[CreateComponentsClouds] Original cloud has been automatically hidden"));
- }
- }
- }
- void MainWindow::doActionLabelConnectedComponents()
- {
- //keep only the point clouds!
- std::vector<ccGenericPointCloud*> clouds;
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
- clouds.push_back(ccHObjectCaster::ToGenericPointCloud(entity));
- }
- }
- size_t count = clouds.size();
- if (count == 0)
- return;
- static int s_octreeLevel = 8;
- static unsigned s_minComponentSize = 10;
- static bool s_randomColors = false;
- ccLabelingDlg dlg(this);
- if (count == 1)
- dlg.octreeLevelSpinBox->setCloud(clouds.front());
- dlg.setOctreeLevel(s_octreeLevel);
- dlg.setMinPointsNb(s_minComponentSize);
- dlg.setRandomColors(s_randomColors);
- if (!dlg.exec())
- return;
- s_octreeLevel = dlg.getOctreeLevel();
- s_minComponentSize = static_cast<unsigned>(std::max(0, dlg.getMinPointsNb()));
- s_randomColors = dlg.randomColors();
- ccProgressDialog pDlg(false, this);
- pDlg.setAutoClose(false);
- //we unselect all entities as we are going to automatically select the created components
- //(otherwise the user won't perceive the change!)
- if (m_ccRoot)
- {
- m_ccRoot->unselectAllEntities();
- }
- for ( ccGenericPointCloud *cloud : clouds )
- {
- if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
- ccOctree::Shared theOctree = cloud->getOctree();
- if (!theOctree)
- {
- ccProgressDialog pOctreeDlg(true, this);
- theOctree = cloud->computeOctree(&pOctreeDlg);
- if (!theOctree)
- {
- ccConsole::Error(tr("Couldn't compute octree for cloud '%1'!").arg(cloud->getName()));
- break;
- }
- }
- //we create/activate CCs label scalar field
- int sfIdx = pc->getScalarFieldIndexByName(CC_CONNECTED_COMPONENTS_DEFAULT_LABEL_NAME);
- if (sfIdx < 0)
- {
- sfIdx = pc->addScalarField(CC_CONNECTED_COMPONENTS_DEFAULT_LABEL_NAME);
- }
- if (sfIdx < 0)
- {
- ccConsole::Error(tr("Couldn't allocate a new scalar field for computing CC labels! Try to free some memory ..."));
- break;
- }
- pc->setCurrentScalarField(sfIdx);
- //we try to label all CCs
- CCCoreLib::ReferenceCloudContainer components;
- int componentCount = CCCoreLib::AutoSegmentationTools::labelConnectedComponents(cloud,
- static_cast<unsigned char>(s_octreeLevel),
- false,
- &pDlg,
- theOctree.data());
- if (componentCount >= 0)
- {
- //if successful, we extract each CC (stored in "components")
- //safety test
- int realComponentCount = 0;
- {
- for (size_t i = 0; i < components.size(); ++i)
- {
- if (components[i]->size() >= s_minComponentSize)
- {
- ++realComponentCount;
- }
- }
- }
- if (realComponentCount > 500)
- {
- //too many components
- if (QMessageBox::warning(this, tr("Many components"), tr("Do you really expect up to %1 components?\n(this may take a lot of time to process and display)").arg(realComponentCount), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
- {
- //cancel
- pc->deleteScalarField(sfIdx);
- if (pc->getNumberOfScalarFields() != 0)
- {
- pc->setCurrentDisplayedScalarField(static_cast<int>(pc->getNumberOfScalarFields()) - 1);
- }
- else
- {
- pc->showSF(false);
- }
- pc->prepareDisplayForRefresh();
- continue;
- }
- }
- pc->getCurrentInScalarField()->computeMinAndMax();
- if (!CCCoreLib::AutoSegmentationTools::extractConnectedComponents(cloud, components))
- {
- ccConsole::Warning(tr("[DoActionLabelConnectedComponents] Something went wrong while extracting CCs from cloud %1...").arg(cloud->getName()));
- }
- }
- else
- {
- ccConsole::Warning(tr("[DoActionLabelConnectedComponents] Something went wrong while extracting CCs from cloud %1...").arg(cloud->getName()));
- }
- //we delete the CCs label scalar field (we don't need it anymore)
- pc->deleteScalarField(sfIdx);
- sfIdx = -1;
- //we create "real" point clouds for all CCs
- if (!components.empty())
- {
- createComponentsClouds(cloud, components, s_minComponentSize, s_randomColors, true);
- }
- }
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSetSFAsCoord()
- {
- if (!ccEntityAction::sfSetAsCoord(m_selectedEntities, this))
- {
- return;
- }
- zoomOnSelectedEntities();
- updateUI();
- }
- void MainWindow::doActionExportCoordToSF()
- {
- if (!ccEntityAction::exportCoordToSF(m_selectedEntities, this))
- {
- return;
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionExportNormalToSF()
- {
- if (!ccEntityAction::exportNormalToSF(m_selectedEntities, this))
- {
- return;
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSetSFsAsNormal()
- {
- if (!haveOneSelection())
- {
- if (haveSelection())
- ccConsole::Error(tr("Select only one cloud or one mesh!"));
- return;
- }
- ccHObject* ent = m_selectedEntities.front();
- if (!ccEntityAction::setSFsAsNormal(ent, this))
- {
- return;
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doMeshTwoPolylines()
- {
- if (m_selectedEntities.size() != 2)
- return;
- ccPolyline* p1 = ccHObjectCaster::ToPolyline(m_selectedEntities.front());
- ccPolyline* p2 = ccHObjectCaster::ToPolyline(m_selectedEntities.back());
- if (!p1 || !p2)
- {
- ccConsole::Error(tr("Select 2 and only 2 polylines"));
- return;
- }
- //Ask the user how the 2D projection should be computed
- bool useViewingDir = false;
- CCVector3 viewingDir(0, 0, 0);
- if (p1->getDisplay())
- {
- useViewingDir = (QMessageBox::question(this, tr("Projection method"), tr("Use best fit plane (yes) or the current viewing direction (no)"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No);
- if (useViewingDir)
- {
- viewingDir = -p1->getDisplay()->getViewportParameters().getViewDir().toPC();
- }
- }
- ccMesh* mesh = ccMesh::TriangulateTwoPolylines(p1, p2, useViewingDir ? &viewingDir : nullptr);
- if (mesh)
- {
- addToDB(mesh);
- if (mesh->computePerVertexNormals())
- {
- mesh->showNormals(true);
- }
- else
- {
- ccLog::Warning(tr("[Mesh two polylines] Failed to compute normals!"));
- }
- if (mesh->getDisplay())
- {
- mesh->getDisplay()->redraw();
- }
- }
- else
- {
- ccLog::Error(tr("Failed to create mesh (see Console)"));
- forceConsoleDisplay();
- }
- }
- void MainWindow::doConvertPolylinesToMesh()
- {
- if (!haveSelection())
- return;
- std::vector<ccPolyline*> polylines;
- try
- {
- if (haveOneSelection() && m_selectedEntities.back()->isA(CC_TYPES::HIERARCHY_OBJECT))
- {
- ccHObject* obj = m_selectedEntities.back();
- for (unsigned i = 0; i < obj->getChildrenNumber(); ++i)
- {
- if (obj->getChild(i)->isA(CC_TYPES::POLY_LINE))
- polylines.push_back(static_cast<ccPolyline*>(obj->getChild(i)));
- }
- }
- else
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isA(CC_TYPES::POLY_LINE))
- {
- polylines.push_back(static_cast<ccPolyline*>(entity));
- }
- }
- }
- }
- catch (const std::bad_alloc&)
- {
- ccConsole::Error(tr("Not enough memory!"));
- return;
- }
- if (polylines.empty())
- {
- ccConsole::Error(tr("Select a group of polylines or multiple polylines (contour plot)!"));
- return;
- }
- ccPickOneElementDlg poeDlg(tr("Projection dimension"), tr("Contour plot to mesh"), this);
- poeDlg.addElement("X");
- poeDlg.addElement("Y");
- poeDlg.addElement("Z");
- poeDlg.setDefaultIndex(2);
- if (!poeDlg.exec())
- return;
- int dim = poeDlg.getSelectedIndex();
- assert(dim >= 0 && dim < 3);
- const unsigned char Z = static_cast<unsigned char>(dim);
- const unsigned char X = (Z == 2 ? 0 : Z + 1);
- const unsigned char Y = (X == 2 ? 0 : X + 1);
- //number of segments
- unsigned segmentCount = 0;
- unsigned vertexCount = 0;
- {
- for ( ccPolyline *poly : polylines )
- {
- if (poly)
- {
- //count the total number of vertices and segments
- vertexCount += poly->size();
- segmentCount += poly->segmentCount();
- }
- }
- }
- if (segmentCount < 2)
- {
- //not enough points/segments
- ccLog::Error(tr("Not enough segments!"));
- return;
- }
- //we assume we link with CGAL now (if not the call to Delaunay2dMesh::buildMesh will fail anyway)
- std::vector<CCVector2> points2D;
- std::vector<int> segments2D;
- try
- {
- points2D.reserve(vertexCount);
- segments2D.reserve(segmentCount * 2);
- }
- catch (const std::bad_alloc&)
- {
- //not enough memory
- ccLog::Error(tr("Not enough memory!"));
- return;
- }
- //fill arrays
- {
- for ( ccPolyline *poly : polylines )
- {
- if (poly == nullptr)
- continue;
-
- unsigned vertCount = poly->size();
- int vertIndex0 = static_cast<int>(points2D.size());
- bool closed = poly->isClosed();
- for (unsigned v = 0; v < vertCount; ++v)
- {
- const CCVector3* P = poly->getPoint(v);
- int vertIndex = static_cast<int>(points2D.size());
- points2D.push_back(CCVector2(P->u[X], P->u[Y]));
- if (v + 1 < vertCount)
- {
- segments2D.push_back(vertIndex);
- segments2D.push_back(vertIndex + 1);
- }
- else if (closed)
- {
- segments2D.push_back(vertIndex);
- segments2D.push_back(vertIndex0);
- }
- }
- }
- assert(points2D.size() == vertexCount);
- assert(segments2D.size() == segmentCount * 2);
- }
- CCCoreLib::Delaunay2dMesh* delaunayMesh = new CCCoreLib::Delaunay2dMesh;
- std::string errorStr;
- if (!delaunayMesh->buildMesh(points2D, segments2D, errorStr))
- {
- ccLog::Error( tr("Third party library error: %1").arg( QString::fromStdString( errorStr ) ) );
- delete delaunayMesh;
- return;
- }
- ccPointCloud* vertices = new ccPointCloud("vertices");
- if (!vertices->reserve(vertexCount))
- {
- //not enough memory
- ccLog::Error(tr("Not enough memory!"));
- delete vertices;
- delete delaunayMesh;
- return;
- }
- //fill vertices cloud
- {
- for ( ccPolyline *poly : polylines )
- {
- unsigned vertCount = poly->size();
- for (unsigned v = 0; v < vertCount; ++v)
- {
- const CCVector3* P = poly->getPoint(v);
- vertices->addPoint(*P);
- }
- }
- delaunayMesh->linkMeshWith(vertices, false);
- }
- #ifdef QT_DEBUG
- //Test delaunay output
- {
- unsigned vertCount = vertices->size();
- for (unsigned i = 0; i < delaunayMesh->size(); ++i)
- {
- const CCCoreLib::VerticesIndexes* tsi = delaunayMesh->getTriangleVertIndexes(i);
- assert(tsi->i1 < vertCount && tsi->i2 < vertCount && tsi->i3 < vertCount);
- }
- }
- #endif
- ccMesh* mesh = new ccMesh(delaunayMesh, vertices);
- if (mesh->size() != delaunayMesh->size())
- {
- //not enough memory (error will be issued later)
- delete mesh;
- mesh = nullptr;
- }
- //don't need this anymore
- delete delaunayMesh;
- delaunayMesh = nullptr;
- if (mesh)
- {
- mesh->addChild(vertices);
- mesh->setVisible(true);
- vertices->setEnabled(false);
- addToDB(mesh);
- if (mesh->computePerVertexNormals())
- {
- mesh->showNormals(true);
- }
- else
- {
- ccLog::Warning(tr("[Contour plot to mesh] Failed to compute normals!"));
- }
- if (mesh->getDisplay())
- {
- mesh->getDisplay()->redraw();
- }
- //global shift & scale (we copy it from the first polyline by default)
- mesh->copyGlobalShiftAndScale(*polylines.front());
- }
- else
- {
- ccLog::Error(tr("Not enough memory!"));
- delete vertices;
- vertices = nullptr;
- }
- }
- void MainWindow::doCompute2HalfDimVolume()
- {
- if (m_selectedEntities.empty() || m_selectedEntities.size() > 2)
- {
- ccConsole::Error(tr("Select one or two point clouds!"));
- return;
- }
- ccGenericPointCloud* cloud1 = nullptr;
- {
- ccHObject* ent = m_selectedEntities.front();
- if (!ent->isKindOf(CC_TYPES::POINT_CLOUD) )
- {
- ccConsole::Error(tr("Select point clouds only!"));
- return;
- }
- else
- {
- cloud1 = ccHObjectCaster::ToGenericPointCloud(ent);
- }
- }
- ccGenericPointCloud* cloud2 = nullptr;
- if (m_selectedEntities.size() > 1)
- {
- ccHObject* ent = m_selectedEntities[1];
- if (!ent->isKindOf(CC_TYPES::POINT_CLOUD) )
- {
- ccConsole::Error(tr("Select point clouds only!"));
- return;
- }
- else
- {
- cloud2 = ccHObjectCaster::ToGenericPointCloud(ent);
- }
- }
- ccVolumeCalcTool calcVolumeTool(cloud1, cloud2, this);
- calcVolumeTool.exec();
- }
- void MainWindow::doActionRasterize()
- {
- if (!haveOneSelection())
- {
- ccConsole::Error(tr("Select only one point cloud!"));
- return;
- }
- ccHObject* ent = m_selectedEntities.front();
- if (!ent->isKindOf(CC_TYPES::POINT_CLOUD) )
- {
- ccConsole::Error(tr("Select a point cloud!"));
- return;
- }
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent);
- ccRasterizeTool rasterizeTool(cloud, this);
- rasterizeTool.exec();
- }
- void MainWindow::doActionDeleteScanGrids()
- {
- //look for clouds with scan grids
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (!entity || !entity->isA(CC_TYPES::POINT_CLOUD))
- {
- continue;
- }
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- assert(cloud);
- if(cloud->gridCount() > 0)
- {
- cloud->removeGrids();
- }
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionMeshScanGrids()
- {
- //ask the user for the min angle (inside triangles)
- static double s_meshMinTriangleAngle_deg = 1.0;
- {
- bool ok = true;
- double minAngle_deg = QInputDialog::getDouble(this, tr("Triangulate"), tr("Min triangle angle (in degrees)"), s_meshMinTriangleAngle_deg, 0, 90.0, 3, &ok);
- if (!ok)
- return;
- s_meshMinTriangleAngle_deg = minAngle_deg;
- }
- //look for clouds with scan grids
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (!entity || !entity->isA(CC_TYPES::POINT_CLOUD))
- {
- continue;
- }
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- assert(cloud);
- for (size_t i = 0; i < cloud->gridCount(); ++i)
- {
- ccPointCloud::Grid::Shared grid = cloud->grid(i);
- if (!grid)
- {
- assert(false);
- continue;
- }
- ccMesh* gridMesh = cloud->triangulateGrid(*grid, s_meshMinTriangleAngle_deg);
- if (gridMesh)
- {
- cloud->addChild(gridMesh);
- cloud->setVisible(false); //hide the cloud
- gridMesh->setDisplay(cloud->getDisplay());
- addToDB(gridMesh, false, true, false, false);
- gridMesh->prepareDisplayForRefresh();
- }
- }
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionComputeMeshAA()
- {
- doActionComputeMesh(CCCoreLib::DELAUNAY_2D_AXIS_ALIGNED);
- }
- void MainWindow::doActionComputeMeshLS()
- {
- doActionComputeMesh(CCCoreLib::DELAUNAY_2D_BEST_LS_PLANE);
- }
- void MainWindow::doActionComputeMesh(CCCoreLib::TRIANGULATION_TYPES type)
- {
- //ask the user for the max edge length
- static double s_meshMaxEdgeLength = 0.0;
- {
- bool ok = true;
- double maxEdgeLength = QInputDialog::getDouble(this, tr("Triangulate"), tr("Max edge length (0 = no limit)"), s_meshMaxEdgeLength, 0, 1.0e9, 8, &ok);
- if (!ok)
- return;
- s_meshMaxEdgeLength = maxEdgeLength;
- }
- //select candidates
- ccHObject::Container clouds;
- bool hadNormals = false;
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- clouds.push_back(entity);
- if (entity->isA(CC_TYPES::POINT_CLOUD))
- {
- hadNormals |= static_cast<ccPointCloud*>(entity)->hasNormals();
- }
- }
- }
- }
- //if the cloud(s) already had normals, ask the use if wants to update them or keep them as is (can look strange!)
- bool updateNormals = false;
- if (hadNormals)
- {
- updateNormals = (QMessageBox::question( this,
- tr("Keep old normals?"),
- tr("Cloud(s) already have normals. Do you want to update them (yes) or keep the old ones (no)?"),
- QMessageBox::Yes,
- QMessageBox::No ) == QMessageBox::Yes);
- }
- ccProgressDialog pDlg(false, this);
- pDlg.setAutoClose(false);
- pDlg.setWindowTitle(tr("Triangulation"));
- pDlg.setInfo(tr("Triangulation in progress..."));
- pDlg.setRange(0, 0);
- pDlg.show();
- QApplication::processEvents();
- bool errors = false;
- for (size_t i = 0; i < clouds.size(); ++i)
- {
- ccHObject* ent = clouds[i];
- assert(ent->isKindOf(CC_TYPES::POINT_CLOUD));
- //compute mesh
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent);
- ccMesh* mesh = ccMesh::Triangulate( cloud,
- type,
- updateNormals,
- static_cast<PointCoordinateType>(s_meshMaxEdgeLength),
- 2 //XY plane by default
- );
- if (mesh)
- {
- cloud->setVisible(false); //can't disable the cloud as the resulting mesh will be its child!
- cloud->addChild(mesh);
- cloud->prepareDisplayForRefresh_recursive();
- addToDB(mesh);
- if (i == 0)
- {
- m_ccRoot->selectEntity(mesh); //auto-select first element
- }
- }
- else
- {
- errors = true;
- }
- }
- if (errors)
- {
- ccConsole::Error(tr("Error(s) occurred! See the Console messages"));
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionFitQuadric()
- {
- bool errors = false;
-
- //for all selected entities
- for ( ccHObject *entity : getSelectedEntities() )
- {
- //look for clouds
- if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity);
- double rms = 0.0;
- ccQuadric* quadric = ccQuadric::Fit(cloud, &rms);
- if (quadric)
- {
- cloud->addChild(quadric);
- quadric->setName(QString("Quadric (%1)").arg(cloud->getName()));
- quadric->setDisplay(cloud->getDisplay());
- quadric->prepareDisplayForRefresh();
- quadric->copyGlobalShiftAndScale(*cloud);
- addToDB(quadric);
- ccConsole::Print(tr("[DoActionFitQuadric] Quadric local coordinate system:"));
- ccConsole::Print(quadric->getTransformation().toString(12,' ')); //full precision
- ccConsole::Print(tr("[DoActionFitQuadric] Quadric equation (in local coordinate system): ") + quadric->getEquationString());
- ccConsole::Print(QString("[DoActionFitQuadric] RMS: %1").arg(rms));
- #if 0
- //test: project the input cloud on the quadric
- if (cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* newCloud = static_cast<ccPointCloud*>(cloud)->cloneThis();
- if (newCloud)
- {
- const PointCoordinateType* eq = quadric->getEquationCoefs();
- const Tuple3ub& dims = quadric->getEquationDims();
- const unsigned char dX = dims.x;
- const unsigned char dY = dims.y;
- const unsigned char dZ = dims.z;
- const ccGLMatrix& trans = quadric->getTransformation();
- ccGLMatrix invTrans = trans.inverse();
- for (unsigned i=0; i<newCloud->size(); ++i)
- {
- CCVector3* P = const_cast<CCVector3*>(newCloud->getPoint(i));
- CCVector3 Q = invTrans * (*P);
- Q.u[dZ] = eq[0] + eq[1]*Q.u[dX] + eq[2]*Q.u[dY] + eq[3]*Q.u[dX]*Q.u[dX] + eq[4]*Q.u[dX]*Q.u[dY] + eq[5]*Q.u[dY]*Q.u[dY];
- *P = trans * Q;
- }
- newCloud->invalidateBoundingBox();
- newCloud->setName(newCloud->getName() + ".projection_on_quadric");
- addToDB(newCloud);
- }
- }
- #endif
- }
- else
- {
- ccConsole::Warning(tr("Failed to compute quadric on cloud '%1'").arg(cloud->getName()));
- errors = true;
- }
- }
- }
- if (errors)
- {
- ccConsole::Error(tr("Error(s) occurred: see console"));
- }
- refreshAll();
- }
- void MainWindow::doActionComputeDistanceMap()
- {
- static unsigned steps = 128;
- static double margin = 0.0;
- static bool filterRange = false;
- static double range[2] = { 0.0, 1.0 };
- //show dialog
- {
- QDialog dialog(this);
- Ui_DistanceMapDialog ui;
- ui.setupUi(&dialog);
- ui.stepsSpinBox->setValue(static_cast<int>(steps));
- ui.marginDoubleSpinBox->setValue(margin);
- ui.rangeCheckBox->setChecked(filterRange);
- ui.minDistDoubleSpinBox->setValue(range[0]);
- ui.maxDistDoubleSpinBox->setValue(range[1]);
- if (!dialog.exec())
- {
- return;
- }
- steps = static_cast<unsigned>(ui.stepsSpinBox->value());
- margin = ui.marginDoubleSpinBox->value();
- filterRange = ui.rangeCheckBox->isChecked();
- range[0] = ui.minDistDoubleSpinBox->value();
- range[1] = ui.maxDistDoubleSpinBox->value();
- }
- ccProgressDialog pDlg(true, this);
- pDlg.setAutoClose(false);
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (!entity->isKindOf(CC_TYPES::MESH) && !entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- //non handled entity type
- continue;
- }
- //CCCoreLib::ChamferDistanceTransform cdt;
- CCCoreLib::SaitoSquaredDistanceTransform cdt;
- if (!cdt.initGrid(Tuple3ui(steps, steps, steps)))
- {
- //not enough memory
- ccLog::Error(tr("Not enough memory!"));
- return;
- }
- ccBBox box = entity->getOwnBB();
- PointCoordinateType largestDim = box.getMaxBoxDim() + static_cast<PointCoordinateType>(margin);
- CCVector3 minCorner = box.getCenter() - CCVector3(1, 1, 1) * (largestDim / 2);
- PointCoordinateType cellDim = largestDim / steps + std::numeric_limits<PointCoordinateType>::epsilon(); //to avoid rounding issues when projecting triangles or points inside the grid
- bool result = false;
- if (entity->isKindOf(CC_TYPES::MESH))
- {
- ccMesh* mesh = static_cast<ccMesh*>(entity);
- result = cdt.initDT(mesh, cellDim, minCorner, &pDlg);
- }
- else
- {
- ccGenericPointCloud* cloud = static_cast<ccGenericPointCloud*>(entity);
- result = cdt.initDT(cloud, cellDim, minCorner, &pDlg);
- }
- if (!result)
- {
- ccLog::Error(tr("Not enough memory!"));
- return;
- }
- //cdt.propagateDistance(CHAMFER_345, &pDlg);
- cdt.propagateDistance(&pDlg);
- //convert the grid to a cloud
- ccPointCloud* gridCloud = new ccPointCloud(entity->getName() + QString(".distance_grid(%1)").arg(steps));
- {
- unsigned pointCount = steps*steps*steps;
- if (!gridCloud->reserve(pointCount))
- {
- ccLog::Error(tr("Not enough memory!"));
- delete gridCloud;
- return;
- }
- ccScalarField* sf = new ccScalarField("DT values");
- if (!sf->reserveSafe(pointCount))
- {
- ccLog::Error(tr("Not enough memory!"));
- delete gridCloud;
- sf->release();
- return;
- }
- for (unsigned i = 0; i < steps; ++i)
- {
- for (unsigned j = 0; j < steps; ++j)
- {
- for (unsigned k = 0; k < steps; ++k)
- {
- ScalarType d = std::sqrt(static_cast<ScalarType>(cdt.getValue(i, j, k))) * cellDim;
- if (!filterRange || (d >= range[0] && d <= range[1]))
- {
- gridCloud->addPoint(minCorner + CCVector3(i + 0.5, j + 0.5, k + 0.5) * cellDim);
- sf->addElement(d);
- }
- }
- }
- }
- sf->computeMinAndMax();
- int sfIdx = gridCloud->addScalarField(sf);
- if (gridCloud->size() == 0)
- {
- ccLog::Warning(tr("[DistanceMap] Cloud '%1': no point falls inside the specified range").arg(entity->getName()));
- delete gridCloud;
- gridCloud = nullptr;
- }
- else
- {
- gridCloud->setCurrentDisplayedScalarField(sfIdx);
- gridCloud->showSF(true);
- gridCloud->setDisplay(entity->getDisplay());
- gridCloud->shrinkToFit();
- entity->prepareDisplayForRefresh();
- addToDB(gridCloud);
- }
- }
- }
- refreshAll();
- }
- void MainWindow::doActionComputeDistToBestFitQuadric3D()
- {
- bool ok = true;
- int steps = QInputDialog::getInt(this, tr("Distance to best fit quadric (3D)"), tr("Steps (per dim.)"), 50, 10, 10000, 10, &ok);
- if (!ok)
- return;
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity);
- CCCoreLib::Neighbourhood Yk(cloud);
- double Q[10];
- if (Yk.compute3DQuadric(Q))
- {
- const double& a = Q[0];
- const double& b = Q[1];
- const double& c = Q[2];
- const double& e = Q[3];
- const double& f = Q[4];
- const double& g = Q[5];
- const double& l = Q[6];
- const double& m = Q[7];
- const double& n = Q[8];
- const double& d = Q[9];
- //gravity center
- const CCVector3* G = Yk.getGravityCenter();
- if (!G)
- {
- ccConsole::Warning(tr("Failed to get the center of gravity of cloud '%1'!").arg(cloud->getName()));
- continue;
- }
- const ccBBox bbox = cloud->getOwnBB();
- PointCoordinateType maxDim = bbox.getMaxBoxDim();
- CCVector3 C = bbox.getCenter();
- //Sample points on a cube and compute for each of them the distance to the Quadric
- ccPointCloud* newCloud = new ccPointCloud();
- if (!newCloud->reserve(steps*steps*steps))
- {
- ccConsole::Error(tr("Not enough memory!"));
- }
- const char defaultSFName[] = "Dist. to 3D quadric";
- int sfIdx = newCloud->getScalarFieldIndexByName(defaultSFName);
- if (sfIdx < 0)
- sfIdx = newCloud->addScalarField(defaultSFName);
- if (sfIdx < 0)
- {
- ccConsole::Error(tr("Couldn't allocate a new scalar field for computing distances! Try to free some memory ..."));
- delete newCloud;
- continue;
- }
- ccScalarField* sf = static_cast<ccScalarField*>(newCloud->getScalarField(sfIdx));
- assert(sf);
- for (int x = 0; x < steps; ++x)
- {
- CCVector3 P;
- P.x = C.x + maxDim * (static_cast<PointCoordinateType>(x) / static_cast<PointCoordinateType>(steps - 1) - CCCoreLib::PC_ONE / 2);
- for (int y = 0; y < steps; ++y)
- {
- P.y = C.y + maxDim * (static_cast<PointCoordinateType>(y) / static_cast<PointCoordinateType>(steps - 1) - CCCoreLib::PC_ONE / 2);
- for (int z = 0; z < steps; ++z)
- {
- P.z = C.z + maxDim * (static_cast<PointCoordinateType>(z) / static_cast<PointCoordinateType>(steps - 1) - CCCoreLib::PC_ONE / 2);
- newCloud->addPoint(P);
- //compute distance to quadric
- CCVector3 Pc = P-*G;
- ScalarType dist = static_cast<ScalarType>( a*Pc.x*Pc.x + b*Pc.y*Pc.y + c*Pc.z*Pc.z
- + e*Pc.x*Pc.y + f*Pc.y*Pc.z + g*Pc.x*Pc.z
- + l*Pc.x + m*Pc.y + n*Pc.z + d);
- sf->addElement(dist);
- }
- }
- }
- if (sf)
- {
- sf->computeMinAndMax();
- newCloud->setCurrentDisplayedScalarField(sfIdx);
- newCloud->showSF(true);
- }
- newCloud->setName(tr("Distance map to 3D quadric"));
- newCloud->setDisplay(cloud->getDisplay());
- newCloud->prepareDisplayForRefresh();
- addToDB(newCloud);
- }
- else
- {
- ccConsole::Warning(tr("Failed to compute 3D quadric on cloud '%1'").arg(cloud->getName()));
- }
- }
- }
- refreshAll();
- }
- void MainWindow::doActionComputeCPS()
- {
- if (m_selectedEntities.size() != 2)
- {
- ccConsole::Error(tr("Select 2 point clouds!"));
- return;
- }
- if (!m_selectedEntities.front()->isKindOf(CC_TYPES::POINT_CLOUD) ||
- !m_selectedEntities.back()->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccConsole::Error(tr("Select 2 point clouds!"));
- return;
- }
- ccOrderChoiceDlg dlg( m_selectedEntities.front(), tr("Compared"),
- m_selectedEntities.back(), tr("Reference"),
- this );
- if (!dlg.exec())
- return;
- ccGenericPointCloud* compCloud = ccHObjectCaster::ToGenericPointCloud(dlg.getFirstEntity());
- ccGenericPointCloud* srcCloud = ccHObjectCaster::ToGenericPointCloud(dlg.getSecondEntity());
- if (!compCloud->isA(CC_TYPES::POINT_CLOUD)) //TODO
- {
- ccConsole::Error(tr("Compared cloud must be a real point cloud!"));
- return;
- }
- ccPointCloud* cmpPC = static_cast<ccPointCloud*>(compCloud);
- static const char DEFAULT_CPS_TEMP_SF_NAME[] = "CPS temporary";
- int sfIdx = cmpPC->getScalarFieldIndexByName(DEFAULT_CPS_TEMP_SF_NAME);
- if (sfIdx < 0)
- sfIdx = cmpPC->addScalarField(DEFAULT_CPS_TEMP_SF_NAME);
- if (sfIdx < 0)
- {
- ccConsole::Error(tr("Couldn't allocate a new scalar field for computing distances! Try to free some memory ..."));
- return;
- }
- cmpPC->setCurrentScalarField(sfIdx);
- if (!cmpPC->enableScalarField())
- {
- ccConsole::Error(tr("Not enough memory!"));
- return;
- }
- //cmpPC->forEach(CCCoreLib::ScalarFieldTools::SetScalarValueToNaN); //now done by default by computeCloud2CloudDistances
- CCCoreLib::ReferenceCloud CPSet(srcCloud);
- ccProgressDialog pDlg(true, this);
- CCCoreLib::DistanceComputationTools::Cloud2CloudDistancesComputationParams params;
- params.CPSet = &CPSet;
- int result = CCCoreLib::DistanceComputationTools::computeCloud2CloudDistances(compCloud, srcCloud, params, &pDlg);
- cmpPC->deleteScalarField(sfIdx);
- if (result >= 0)
- {
- ccPointCloud* newCloud = nullptr;
- //if the source cloud is a "true" cloud, the extracted CPS
- //will also get its attributes
- newCloud = srcCloud->isA(CC_TYPES::POINT_CLOUD) ? static_cast<ccPointCloud*>(srcCloud)->partialClone(&CPSet) : ccPointCloud::From(&CPSet, srcCloud);
- newCloud->setName(QString("[%1]->CPSet(%2)").arg(srcCloud->getName(), compCloud->getName()));
- newCloud->setDisplay(compCloud->getDisplay());
- newCloud->prepareDisplayForRefresh();
- addToDB(newCloud);
- //we hide the source cloud (for a clearer display)
- srcCloud->setEnabled(false);
- srcCloud->prepareDisplayForRefresh();
- }
- refreshAll();
- }
- void MainWindow::doActionComputeNormals()
- {
- if ( !ccEntityAction::computeNormals(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionOrientNormalsMST()
- {
- if ( !ccEntityAction::orientNormalsMST(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionOrientNormalsFM()
- {
- if ( !ccEntityAction::orientNormalsFM(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionShiftPointsAlongNormals()
- {
- std::vector<ccPointCloud*> candidates;
- for (ccHObject* entity : m_selectedEntities)
- {
- if (entity && entity->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(entity);
- if (pc->hasNormals())
- {
- candidates.push_back(pc);
- }
- }
- }
- if (candidates.empty())
- {
- ccConsole::Error(QObject::tr("Select at least one point cloud with normals"));
- return;
- }
- // ask the user to define the shift value
- static double s_shift = 0.0;
- bool ok = false;
- double shift = QInputDialog::getDouble(this, tr("Shift along normals"), tr("Shift quantity"), s_shift, -1.0e6, 1.0e6, 6, &ok);
- if (!ok)
- {
- return;
- }
- // remember for next time
- s_shift = shift;
- for (ccPointCloud* pc : candidates)
- {
- pc->shiftPointsAlongNormals(static_cast<PointCoordinateType>(shift));
- pc->prepareDisplayForRefresh();
- }
- refreshAll();
- }
- void MainWindow::doActionFindBiggestInnerRectangle()
- {
- if (!haveSelection())
- return;
- ccHObject* entity = haveOneSelection() ? m_selectedEntities.front() : nullptr;
- if (!entity || !entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccConsole::Error(tr("Select one point cloud!"));
- return;
- }
- bool ok;
- static int s_innerRectDim = 2;
- int dim = QInputDialog::getInt(this, tr("Dimension"), tr("Orthogonal dim (X=0 / Y=1 / Z=2)"), s_innerRectDim, 0, 2, 1, &ok);
- if (!ok)
- return;
- s_innerRectDim = dim;
- ccGenericPointCloud* cloud = static_cast<ccGenericPointCloud*>(entity);
- ccBox* box = ccInnerRect2DFinder().process(cloud,static_cast<unsigned char>(dim));
- if (box)
- {
- cloud->addChild(box);
- box->setVisible(true);
- box->setDisplay(cloud->getDisplay());
- box->setDisplay(cloud->getDisplay());
- addToDB(box);
- }
- updateUI();
- }
- void MainWindow::doActionFastRegistration(FastRegistrationMode mode)
- {
- //we need at least 1 entity
- if (m_selectedEntities.empty())
- return;
- //we must backup 'm_selectedEntities' as removeObjectTemporarilyFromDBTree can modify it!
- ccHObject::Container selectedEntities = m_selectedEntities;
- for (ccHObject *entity : selectedEntities)
- {
- ccBBox box = entity->getBB_recursive();
- CCVector3 T; //translation
- switch (mode)
- {
- case MoveBBCenterToOrigin:
- T = -box.getCenter();
- break;
- case MoveBBMinCornerToOrigin:
- T = -box.minCorner();
- break;
- case MoveBBMaxCornerToOrigin:
- T = -box.maxCorner();
- break;
- default:
- assert(false);
- return;
- }
- //transformation (used only for translation)
- ccGLMatrix glTrans;
- glTrans.setTranslation(T);
- forceConsoleDisplay();
- ccConsole::Print(tr("[Synchronize] Transformation matrix (%1):").arg(entity->getName()));
- ccConsole::Print(glTrans.toString(12, ' ')); //full precision
- ccConsole::Print(tr("Hint: copy it (CTRL+C) and apply it - or its inverse - on any entity with the 'Edit > Apply transformation' tool"));
- //we temporarily detach the entity, as it may undergo
- //'severe' modifications (octree deletion, etc.) --> see ccHObject::applyGLTransformation
- ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(entity);
- entity->applyGLTransformation_recursive(&glTrans);
- putObjectBackIntoDBTree(entity, objContext);
- entity->prepareDisplayForRefresh_recursive();
- }
- //reselect previously selected entities!
- if (m_ccRoot)
- m_ccRoot->selectEntities(selectedEntities);
- zoomOnSelectedEntities();
- updateUI();
- }
- void MainWindow::doActionMatchBBCenters()
- {
- //we need at least 2 entities
- if (m_selectedEntities.size() < 2)
- return;
- //we must backup 'm_selectedEntities' as removeObjectTemporarilyFromDBTree can modify it!
- ccHObject::Container selectedEntities = m_selectedEntities;
- //by default, we take the first entity as reference
- //TODO: maybe the user would like to select the reference himself ;)
- ccHObject* refEnt = selectedEntities[0];
- CCVector3 refCenter = refEnt->getBB_recursive().getCenter();
- for (ccHObject *entity : selectedEntities) //warning, getSelectedEntites may change during this loop!
- {
- CCVector3 center = entity->getBB_recursive().getCenter();
- CCVector3 T = refCenter-center;
- //transformation (used only for translation)
- ccGLMatrix glTrans;
- glTrans += T;
- forceConsoleDisplay();
- ccConsole::Print(tr("[Synchronize] Transformation matrix (%1 --> %2):").arg(entity->getName(),selectedEntities[0]->getName()));
- ccConsole::Print(glTrans.toString(12,' ')); //full precision
- ccConsole::Print(tr("Hint: copy it (CTRL+C) and apply it - or its inverse - on any entity with the 'Edit > Apply transformation' tool"));
- //we temporarily detach the entity, as it may undergo
- //'severe' modifications (octree deletion, etc.) --> see ccHObject::applyGLTransformation
- ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(entity);
- entity->applyGLTransformation_recursive(&glTrans);
- putObjectBackIntoDBTree(entity, objContext);
- entity->prepareDisplayForRefresh_recursive();
- }
- //reselect previously selected entities!
- if (m_ccRoot)
- m_ccRoot->selectEntities(selectedEntities);
- zoomOnSelectedEntities();
- updateUI();
- }
- //semi-persistent parameters
- static ccLibAlgorithms::ScaleMatchingAlgorithm s_msAlgorithm = ccLibAlgorithms::PCA_MAX_DIM;
- static double s_msRmsDiff = 1.0e-5;
- static int s_msFinalOverlap = 100;
- void MainWindow::doActionMatchScales()
- {
- //we need at least 2 entities
- if (m_selectedEntities.size() < 2)
- return;
- //we must select the point clouds and meshes
- ccHObject::Container selectedEntities;
- try
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if ( entity->isKindOf(CC_TYPES::POINT_CLOUD)
- || entity->isKindOf(CC_TYPES::MESH))
- {
- selectedEntities.push_back(entity);
- }
- }
- }
- catch (const std::bad_alloc&)
- {
- ccConsole::Error(tr("Not enough memory!"));
- return;
- }
- ccMatchScalesDlg msDlg(selectedEntities, 0, this);
- msDlg.setSelectedAlgorithm(s_msAlgorithm);
- msDlg.rmsDifferenceLineEdit->setText(QString::number(s_msRmsDiff, 'e', 1));
- msDlg.overlapSpinBox->setValue(s_msFinalOverlap);
- if (!msDlg.exec())
- return;
- //save semi-persistent parameters
- s_msAlgorithm = msDlg.getSelectedAlgorithm();
- if (s_msAlgorithm == ccLibAlgorithms::ICP_SCALE)
- {
- s_msRmsDiff = msDlg.rmsDifferenceLineEdit->text().toDouble();
- s_msFinalOverlap = msDlg.overlapSpinBox->value();
- }
- ccLibAlgorithms::ApplyScaleMatchingAlgorithm( s_msAlgorithm,
- selectedEntities,
- s_msRmsDiff,
- s_msFinalOverlap,
- msDlg.getSelectedIndex(),
- this);
- //reselect previously selected entities!
- if (m_ccRoot)
- m_ccRoot->selectEntities(selectedEntities);
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSORFilter()
- {
- ccSORFilterDlg sorDlg(this);
- //set semi-persistent/dynamic parameters
- static int s_sorFilterKnn = 6;
- static double s_sorFilterNSigma = 1.0;
- sorDlg.setKNN(s_sorFilterKnn);
- sorDlg.setNSigma(s_sorFilterNSigma);
- if (!sorDlg.exec())
- {
- return;
- }
- //update semi-persistent/dynamic parameters
- s_sorFilterKnn = sorDlg.KNN();
- s_sorFilterNSigma = sorDlg.nSigma();
- ccProgressDialog pDlg(true, this);
- pDlg.setAutoClose(false);
- bool firstCloud = true;
- ccHObject::Container selectedEntities = getSelectedEntities(); //we have to use a local copy: 'selectEntity' will change the set of currently selected entities!
- for (ccHObject* entity : selectedEntities)
- {
- //specific test for locked vertices
- bool lockedVertices;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity, &lockedVertices);
- if (!cloud)
- {
- continue;
- }
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(entity->getName(), haveOneSelection());
- continue;
- }
- //computation
- CCCoreLib::ReferenceCloud* selection = CCCoreLib::CloudSamplingTools::sorFilter(cloud,
- s_sorFilterKnn,
- s_sorFilterNSigma,
- cloud->getOctree().data(),
- &pDlg);
- if (selection)
- {
- if (selection->size() == cloud->size())
- {
- ccLog::Warning(tr("[DoActionSORFilter] No points were removed from cloud '%1'").arg(cloud->getName()));
- }
- else
- {
- ccPointCloud* cleanCloud = cloud->partialClone(selection);
- if (cleanCloud)
- {
- cleanCloud->setName(cloud->getName() + QString(".clean"));
- cleanCloud->setDisplay(cloud->getDisplay());
- if (cloud->getParent())
- cloud->getParent()->addChild(cleanCloud);
- addToDB(cleanCloud);
- cloud->setEnabled(false);
- if (firstCloud)
- {
- ccConsole::Warning(tr("Previously selected entities (sources) have been hidden!"));
- firstCloud = false;
- m_ccRoot->selectEntity(cleanCloud, true);
- }
- }
- else
- {
- ccConsole::Warning(tr("[DoActionSORFilter] Not enough memory to create a clean version of cloud '%1'!").arg(cloud->getName()));
- }
- }
- delete selection;
- selection = nullptr;
- }
- else
- {
- //no points fall inside selection!
- ccConsole::Warning(tr("[DoActionSORFilter] Failed to apply the noise filter to cloud '%1'! (not enough memory?)").arg(cloud->getName()));
- }
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionFilterNoise()
- {
- double kernelRadius = ccLibAlgorithms::GetDefaultCloudKernelSize(m_selectedEntities);
- ccNoiseFilterDlg noiseDlg(this);
- //set semi-persistent/dynamic parameters
- static bool s_noiseFilterUseKnn = false;
- static int s_noiseFilterKnn = 6;
- static bool s_noiseFilterUseAbsError = false;
- static double s_noiseFilterAbsError = 1.0;
- static double s_noiseFilterNSigma = 1.0;
- static bool s_noiseFilterRemoveIsolatedPoints = false;
- noiseDlg.radiusDoubleSpinBox->setValue(kernelRadius);
- noiseDlg.knnSpinBox->setValue(s_noiseFilterKnn);
- noiseDlg.nSigmaDoubleSpinBox->setValue(s_noiseFilterNSigma);
- noiseDlg.absErrorDoubleSpinBox->setValue(s_noiseFilterAbsError);
- noiseDlg.removeIsolatedPointsCheckBox->setChecked(s_noiseFilterRemoveIsolatedPoints);
- if (s_noiseFilterUseAbsError)
- noiseDlg.absErrorRadioButton->setChecked(true);
- else
- noiseDlg.relativeRadioButton->setChecked(true);
- if (s_noiseFilterUseKnn)
- noiseDlg.knnRadioButton->setChecked(true);
- else
- noiseDlg.radiusRadioButton->setChecked(true);
- if (!noiseDlg.exec())
- return;
- //update semi-persistent/dynamic parameters
- kernelRadius = noiseDlg.radiusDoubleSpinBox->value();
- s_noiseFilterUseKnn = noiseDlg.knnRadioButton->isChecked();
- s_noiseFilterKnn = noiseDlg.knnSpinBox->value();
- s_noiseFilterUseAbsError = noiseDlg.absErrorRadioButton->isChecked();
- s_noiseFilterNSigma = noiseDlg.nSigmaDoubleSpinBox->value();
- s_noiseFilterAbsError = noiseDlg.absErrorDoubleSpinBox->value();
- s_noiseFilterRemoveIsolatedPoints = noiseDlg.removeIsolatedPointsCheckBox->isChecked();
- ccProgressDialog pDlg(true, this);
- pDlg.setAutoClose(false);
- bool firstCloud = true;
-
- ccHObject::Container selectedEntities = getSelectedEntities(); //we have to use a local copy: and 'selectEntity' will change the set of currently selected entities!
-
- for ( ccHObject *entity : selectedEntities )
- {
- //specific test for locked vertices
- bool lockedVertices;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity, &lockedVertices);
- if (!cloud)
- {
- continue;
- }
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(entity->getName(), haveOneSelection());
- continue;
- }
- //computation
- CCCoreLib::ReferenceCloud* selection = CCCoreLib::CloudSamplingTools::noiseFilter( cloud,
- static_cast<PointCoordinateType>(kernelRadius),
- s_noiseFilterNSigma,
- s_noiseFilterRemoveIsolatedPoints,
- s_noiseFilterUseKnn,
- s_noiseFilterKnn,
- s_noiseFilterUseAbsError,
- s_noiseFilterAbsError,
- cloud->getOctree().data(),
- &pDlg);
- if (selection)
- {
- if (selection->size() == cloud->size())
- {
- ccLog::Warning(tr("[DoActionFilterNoise] No points were removed from cloud '%1'").arg(cloud->getName()));
- }
- else
- {
- ccPointCloud* cleanCloud = cloud->partialClone(selection);
- if (cleanCloud)
- {
- cleanCloud->setName(cloud->getName() + QString(".clean"));
- cleanCloud->setDisplay(cloud->getDisplay());
- if (cloud->getParent())
- cloud->getParent()->addChild(cleanCloud);
- addToDB(cleanCloud);
- cloud->setEnabled(false);
- if (firstCloud)
- {
- ccConsole::Warning(tr("Previously selected entities (sources) have been hidden!"));
- firstCloud = false;
- m_ccRoot->selectEntity(cleanCloud, true);
- }
- }
- else
- {
- ccConsole::Warning(tr("[DoActionFilterNoise] Not enough memory to create a clean version of cloud '%1'!").arg(cloud->getName()));
- }
- }
- delete selection;
- selection = nullptr;
- }
- else
- {
- //no points fall inside selection!
- ccConsole::Warning(tr("[DoActionFilterNoise] Failed to apply the noise filter to cloud '%1'! (not enough memory?)").arg(cloud->getName()));
- }
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionUnroll()
- {
- //there should be only one point cloud or one mesh!
- if (!haveOneSelection())
- {
- ccConsole::Error(tr("Select one and only one entity!"));
- return;
- }
- //if selected entity is a mesh, the method will be applied to its vertices
- bool lockedVertices;
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(m_selectedEntities.front(), &lockedVertices);
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(m_selectedEntities.front()->getName(), true);
- return;
- }
- //for "real" point clouds only
- if (!cloud || !cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- ccConsole::Error(tr("Method can't be applied on locked vertices or virtual point clouds!"));
- return;
- }
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
- ccUnrollDlg unrollDlg(m_ccRoot ? m_ccRoot->getRootEntity() : nullptr, this);
- unrollDlg.fromPersistentSettings();
- if (!unrollDlg.exec())
- return;
- unrollDlg.toPersistentSettings();
- ccPointCloud::UnrollMode mode = unrollDlg.getType();
- PointCoordinateType radius = static_cast<PointCoordinateType>(unrollDlg.getRadius());
- CCVector3d axisDir = unrollDlg.getAxis();
- bool exportDeviationSF = unrollDlg.exportDeviationSF();
- bool arbitraryOutputCS = unrollDlg.useArbitraryOutputCS();
- CCVector3 center = unrollDlg.getAxisPosition();
- //let's rock unroll ;)
- ccProgressDialog pDlg(true, this);
- double startAngle_deg = 0.0;
- double stopAngle_deg = 360.0;
- unrollDlg.getAngleRange(startAngle_deg, stopAngle_deg);
- if (startAngle_deg >= stopAngle_deg)
- {
- QMessageBox::critical(this, tr("Error"), tr("Invalid angular range"));
- return;
- }
- ccPointCloud* output = nullptr;
- switch (mode)
- {
- case ccPointCloud::CYLINDER:
- {
- ccPointCloud::UnrollCylinderParams params;
- params.radius = radius;
- params.axisDir = CCVector3::fromArray(axisDir.u);
- if (unrollDlg.isAxisPositionAuto())
- {
- center = pc->getOwnBB().getCenter();
- }
- params.center = center;
- output = pc->unroll(mode, ¶ms, exportDeviationSF, startAngle_deg, stopAngle_deg, arbitraryOutputCS, &pDlg);
- }
- break;
- case ccPointCloud::CONE_CONICAL:
- case ccPointCloud::CONE_CYLINDRICAL_FIXED_RADIUS:
- case ccPointCloud::CONE_CYLINDRICAL_ADAPTIVE_RADIUS:
- {
- ccPointCloud::UnrollConeParams params;
- params.radius = (mode == ccPointCloud::CONE_CYLINDRICAL_FIXED_RADIUS ? radius : 0);
- params.apex = center;
- params.coneAngle_deg = unrollDlg.getConeHalfAngle();
- params.axisDir = CCVector3::fromArray(axisDir.u);
- params.spanRatio = unrollDlg.getConicalProjSpanRatio();
- output = pc->unroll(mode, ¶ms, exportDeviationSF, startAngle_deg, stopAngle_deg, arbitraryOutputCS, &pDlg);
- }
- break;
-
- default:
- assert(false);
- break;
- }
- if (output)
- {
- if (m_selectedEntities.front()->isA(CC_TYPES::MESH))
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(m_selectedEntities.front());
- mesh->setEnabled(false);
- ccConsole::Warning(tr("[Unroll] Original mesh has been automatically hidden"));
- ccMesh* outputMesh = mesh->cloneMesh(output);
- outputMesh->addChild(output);
- addToDB(outputMesh, true, true, false, true);
- outputMesh->setEnabled(true);
- outputMesh->setVisible(true);
- }
- else
- {
- pc->setEnabled(false);
- ccConsole::Warning(tr("[Unroll] Original cloud has been automatically hidden"));
- if (pc->getParent())
- {
- pc->getParent()->addChild(output);
- }
- addToDB(output, true, true, false, true);
- }
- updateUI();
- }
- }
- ccGLWindowInterface* MainWindow::getActiveGLWindow()
- {
- if (!m_mdiArea)
- {
- return nullptr;
- }
- QMdiSubWindow *activeSubWindow = m_mdiArea->activeSubWindow();
- if (activeSubWindow)
- {
- return ccGLWindowInterface::FromWidget(activeSubWindow->widget());
- }
- else
- {
- QList<QMdiSubWindow*> subWindowList = m_mdiArea->subWindowList();
- if (!subWindowList.isEmpty())
- {
- return ccGLWindowInterface::FromWidget(subWindowList[0]->widget());
- }
- }
- return nullptr;
- }
- QMdiSubWindow* MainWindow::getMDISubWindow(ccGLWindowInterface* win)
- {
- QList<QMdiSubWindow*> subWindowList = m_mdiArea->subWindowList();
- for (int i = 0; i < subWindowList.size(); ++i)
- {
- if (ccGLWindowInterface::FromWidget(subWindowList[i]->widget()) == win)
- return subWindowList[i];
- }
- //not found!
- return nullptr;
- }
- ccGLWindowInterface* MainWindow::getGLWindow(int index) const
- {
- QList<QMdiSubWindow*> subWindowList = m_mdiArea->subWindowList();
- if (index >= 0 && index < subWindowList.size())
- {
- ccGLWindowInterface* win = ccGLWindowInterface::FromWidget(subWindowList[index]->widget());
- assert(win);
- return win;
- }
- else
- {
- assert(false);
- return nullptr;
- }
- }
- int MainWindow::getGLWindowCount() const
- {
- return m_mdiArea ? m_mdiArea->subWindowList().size() : 0;
- }
- void MainWindow::zoomIn()
- {
- ccGLWindowInterface* win = MainWindow::getActiveGLWindow();
- if (win)
- {
- //we simulate a real wheel event
- win->onWheelEvent(15.0f);
- }
- }
- void MainWindow::zoomOut()
- {
- ccGLWindowInterface* win = MainWindow::getActiveGLWindow();
- if (win)
- {
- //we simulate a real wheel event
- win->onWheelEvent(-15.0f);
- }
- }
- ccGLWindowInterface* MainWindow::new3DViewInternal( bool allowEntitySelection )
- {
- assert(m_ccRoot && m_mdiArea);
- QWidget* viewWidget = nullptr;
- ccGLWindowInterface* view3D = nullptr;
-
- createGLWindow(view3D, viewWidget);
- if (!viewWidget || !view3D)
- {
- ccLog::Error(tr("Failed to create the 3D view"));
- assert(false);
- return nullptr;
- }
- //restore options
- {
- QSettings settings;
- bool autoPickRotationCenter = settings.value(ccPS::AutoPickRotationCenter(), true).toBool();
- view3D->setAutoPickPivotAtCenter(autoPickRotationCenter);
- }
- viewWidget->setMinimumSize(400, 300);
- m_mdiArea->addSubWindow(viewWidget);
- if ( allowEntitySelection )
- {
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::entitySelectionChanged, this, [=] (ccHObject *entity) {
- m_ccRoot->selectEntity( entity );
- });
-
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::entitiesSelectionChanged, this, [=] (std::unordered_set<int> entities){
- m_ccRoot->selectEntities( entities );
- });
- }
- //'echo' mode
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::mouseWheelRotated, this, &MainWindow::echoMouseWheelRotate);
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::viewMatRotated, this, &MainWindow::echoBaseViewMatRotation);
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::cameraPosChanged, this, &MainWindow::echoCameraPosChanged);
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::pivotPointChanged, this, &MainWindow::echoPivotPointChanged);
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::aboutToClose, this, &MainWindow::prepareWindowDeletion);
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::filesDropped, this, &MainWindow::addToDBAuto, Qt::QueuedConnection); //DGM: we don't want to block the 'dropEvent' method of ccGLWindow instances!
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::newLabel, this, &MainWindow::handleNewLabel);
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::exclusiveFullScreenToggled, this, &MainWindow::onExclusiveFullScreenToggled);
- if (m_pickingHub)
- {
- //we must notify the picking hub as well if the window is destroyed
- connect(view3D->signalEmitter(), &ccGLWindowSignalEmitter::aboutToClose, m_pickingHub, &ccPickingHub::onActiveWindowDeleted);
- }
- view3D->setSceneDB(m_ccRoot->getRootEntity());
- viewWidget->setAttribute(Qt::WA_DeleteOnClose);
- m_ccRoot->updatePropertiesView();
- QMainWindow::statusBar()->showMessage(tr("New 3D View"), 2000);
- viewWidget->showMaximized();
- viewWidget->update();
- return view3D;
- }
- void MainWindow::prepareWindowDeletion(ccGLWindowInterface* glWindow)
- {
- if (!m_ccRoot)
- return;
- if (glWindow)
- {
- m_ccRoot->hidePropertiesView();
- m_ccRoot->getRootEntity()->removeFromDisplay_recursive(glWindow);
- m_ccRoot->updatePropertiesView();
- }
- else
- {
- assert(false);
- }
- }
- static bool s_autoSaveGuiElementPos = true;
- void MainWindow::doActionResetGUIElementsPos()
- {
- //show the user it will be maximized
- showMaximized();
- QSettings settings;
- settings.remove(ccPS::MainWinGeom());
- settings.remove(ccPS::MainWinState());
- QMessageBox::information( this,
- tr("Restart"),
- tr("To finish the process, you'll have to close and restart CloudCompare") );
-
- //to avoid saving them right away!
- s_autoSaveGuiElementPos = false;
- }
- void MainWindow::doActionToggleRestoreWindowOnStartup(bool state)
- {
- QSettings settings;
- settings.setValue(ccPS::DoNotRestoreWindowGeometry(), !state);
- }
- void MainWindow::doActionResetAllVBOs()
- {
- ccHObject::Container clouds;
- m_ccRoot->getRootEntity()->filterChildren(clouds, true, CC_TYPES::POINT_CLOUD, true);
- size_t releasedSize = 0;
- for (ccHObject* entity : clouds)
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- if (cloud)
- {
- releasedSize += cloud->vboSize();
- cloud->releaseVBOs();
- }
- }
- if (releasedSize != 0)
- {
- ccLog::Print(tr("All VBOs have been released (%1 Mb)").arg(releasedSize / static_cast<double>(1 << 20), 0, 'f', 2));
- if (ccGui::Parameters().useVBOs)
- {
- ccLog::Warning(tr("You might want to disable the 'use VBOs' option in the Display Settings to keep the GPU memory empty"));
- }
- }
- else
- {
- ccLog::Print(tr("No VBO allocated"));
- }
- }
- void MainWindow::restoreGUIElementsPos()
- {
- QSettings settings;
- QVariant previousState = settings.value(ccPS::MainWinState());
- if (previousState.isValid())
- {
- restoreState(previousState.toByteArray());
- }
- QVariant previousGeometry;
- if (!settings.value(ccPS::DoNotRestoreWindowGeometry(), false).toBool())
- {
- previousGeometry = settings.value(ccPS::MainWinGeom());
- }
- if (previousGeometry.isValid())
- {
- restoreGeometry(previousGeometry.toByteArray());
- }
- else
- {
- showMaximized();
- }
- if (isFullScreen())
- {
- m_UI->actionFullScreen->blockSignals(true);
- m_UI->actionFullScreen->setChecked(true);
- m_UI->actionFullScreen->blockSignals(false);
- }
- }
- void MainWindow::showEvent(QShowEvent* event)
- {
- if (m_firstShow)
- {
- restoreGUIElementsPos();
- m_firstShow = false;
- }
- QMainWindow::showEvent(event);
- }
- void MainWindow::closeEvent(QCloseEvent* event)
- {
- const ccOptions &opts = ccOptions::Instance();
- // If we don't have anything displayed, then just close...
- if (!opts.confirmQuit || (m_ccRoot && (m_ccRoot->getRootEntity()->getChildrenNumber() == 0)))
- {
- event->accept();
- }
- else // ...otherwise confirm
- {
- QMessageBox message_box( QMessageBox::Question,
- tr("Quit"),
- tr("Are you sure you want to quit?"),
- QMessageBox::NoButton,
- this);
- message_box.addButton(QMessageBox::Button::Yes);
- message_box.addButton(tr("Yes, don't ask again"), QMessageBox::ButtonRole::YesRole);
- message_box.addButton(QMessageBox::Button::No);
- message_box.exec();
- switch (message_box.buttons().indexOf(message_box.clickedButton())) {
- case 1: // Yes, don't ask again
- {
- ccOptions optsCopied = opts;
- optsCopied.confirmQuit = false;
- optsCopied.toPersistentSettings();
- Q_FALLTHROUGH(); // Fallthrough to the yes case, to accept the event
- }
- case 0: // Yes
- event->accept();
- break;
- case 2: // No
- default:
- event->ignore();
- }
- }
- if (s_autoSaveGuiElementPos)
- {
- saveGUIElementsPos();
- }
- }
- void MainWindow::saveGUIElementsPos()
- {
- //save the state as settings
- QSettings settings;
- settings.setValue(ccPS::MainWinGeom(), saveGeometry());
- settings.setValue(ccPS::MainWinState(), saveState());
- }
- void MainWindow::moveEvent(QMoveEvent* event)
- {
- QMainWindow::moveEvent(event);
- updateOverlayDialogsPlacement();
- }
- void MainWindow::resizeEvent(QResizeEvent* event)
- {
- QMainWindow::resizeEvent(event);
- updateOverlayDialogsPlacement();
- }
- void MainWindow::registerOverlayDialog(ccOverlayDialog* dlg, Qt::Corner pos)
- {
- //check for existence
- for (ccMDIDialogs& mdi : m_mdiDialogs)
- {
- if (mdi.dialog == dlg)
- {
- //we only update its position in this case
- mdi.position = pos;
- repositionOverlayDialog(mdi);
- return;
- }
- }
- //otherwise we add it to DB
- m_mdiDialogs.push_back(ccMDIDialogs(dlg, pos));
- //automatically update the dialog placement when its shown
- connect(dlg, &ccOverlayDialog::shown, this, [=]()
- {
- //check for existence
- for (ccMDIDialogs& mdi : m_mdiDialogs)
- {
- if (mdi.dialog == dlg)
- {
- repositionOverlayDialog(mdi);
- break;
- }
- }
- });
- repositionOverlayDialog(m_mdiDialogs.back());
- }
- void MainWindow::unregisterOverlayDialog(ccOverlayDialog* dialog)
- {
- for (std::vector<ccMDIDialogs>::iterator it = m_mdiDialogs.begin(); it != m_mdiDialogs.end(); ++it)
- {
- if (it->dialog == dialog)
- {
- m_mdiDialogs.erase(it);
- break;
- }
- }
- }
- bool MainWindow::eventFilter(QObject *obj, QEvent *event)
- {
- switch (event->type())
- {
- case QEvent::Resize:
- case QEvent::Move:
- updateOverlayDialogsPlacement();
- break;
- default:
- //nothing to do
- break;
- }
- // standard event processing
- return QObject::eventFilter(obj, event);
- }
- void MainWindow::keyPressEvent(QKeyEvent *event)
- {
- switch (event->key())
- {
- case Qt::Key_Escape:
- {
- if ( s_pickingWindow != nullptr )
- {
- cancelPreviousPickingOperation( true );
- }
- break;
- }
-
- default:
- QMainWindow::keyPressEvent(event);
- }
- }
- void MainWindow::updateOverlayDialogsPlacement()
- {
- for (ccMDIDialogs& mdiDlg : m_mdiDialogs)
- {
- repositionOverlayDialog(mdiDlg);
- }
- }
- void MainWindow::repositionOverlayDialog(ccMDIDialogs& mdiDlg)
- {
- if (!mdiDlg.dialog || !mdiDlg.dialog->isVisible() || !m_mdiArea)
- return;
- int dx = 0;
- int dy = 0;
- static const int margin = 5;
- switch (mdiDlg.position)
- {
- case Qt::TopLeftCorner:
- dx = margin;
- dy = margin;
- break;
- case Qt::TopRightCorner:
- dx = std::max(margin, m_mdiArea->width() - mdiDlg.dialog->width() - margin);
- dy = margin;
- break;
- case Qt::BottomLeftCorner:
- dx = margin;
- dy = std::max(margin, m_mdiArea->height() - mdiDlg.dialog->height() - margin);
- break;
- case Qt::BottomRightCorner:
- dx = std::max(margin, m_mdiArea->width() - mdiDlg.dialog->width() - margin);
- dy = std::max(margin, m_mdiArea->height() - mdiDlg.dialog->height() - margin);
- break;
- }
- //show();
- mdiDlg.dialog->move(m_mdiArea->mapToGlobal(QPoint(dx, dy)));
- mdiDlg.dialog->raise();
- }
- void MainWindow::toggleVisualDebugTraces()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->toggleDebugTrace();
- win->redraw(false, false);
- }
- }
- void MainWindow::toggleFullScreen(bool state)
- {
- if (state)
- showFullScreen();
- else
- showNormal();
- #ifdef Q_OS_MAC
- if ( state )
- {
- m_UI->actionFullScreen->setText( tr( "Exit Full Screen" ) );
- }
- else
- {
- m_UI->actionFullScreen->setText( tr( "Enter Full Screen" ) );
- }
- #endif
- }
- void MainWindow::toggleExclusiveFullScreen(bool state)
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->toggleExclusiveFullScreen(state);
- }
- }
- void MainWindow::doActionShowHelpDialog()
- {
- QMessageBox messageBox;
- messageBox.setTextFormat(Qt::RichText);
- messageBox.setWindowTitle("Documentation");
- messageBox.setText("Please look at the <a href='http://www.cloudcompare.org/doc/wiki'>wiki</a>");
- messageBox.setStandardButtons(QMessageBox::Ok);
- messageBox.exec();
- }
- void MainWindow::freezeUI(bool state)
- {
- //freeze standard plugins
- m_UI->toolBarMainTools->setDisabled(state);
- m_UI->toolBarSFTools->setDisabled(state);
-
- m_pluginUIManager->mainPluginToolbar()->setDisabled(state);
- //freeze plugin toolbars
- for ( QToolBar *toolbar : m_pluginUIManager->additionalPluginToolbars() )
- {
- toolbar->setDisabled(state);
- }
- m_UI->DockableDBTree->setDisabled(state);
- m_UI->menubar->setDisabled(state);
- if (state)
- {
- m_UI->menuEdit->setDisabled(true);
- m_UI->menuTools->setDisabled(true);
- }
- else
- {
- updateMenus();
- }
- m_uiFrozen = state;
- }
- void MainWindow::activateRegisterPointPairTool()
- {
- if (!haveSelection())
- {
- ccConsole::Error(tr("Select at least one entity (point cloud or mesh)!"));
- return;
- }
- ccHObject::Container alignedEntities;
- ccHObject::Container refEntities;
- try
- {
- ccHObject::Container entities;
- entities.reserve(m_selectedEntities.size());
- for (ccHObject* entity : m_selectedEntities)
- {
- //for now, we only handle clouds or meshes
- if (entity->isKindOf(CC_TYPES::POINT_CLOUD) || entity->isKindOf(CC_TYPES::MESH))
- {
- entities.push_back(entity);
- }
- }
- if (entities.empty())
- {
- ccConsole::Error("Select at least one entity (point cloud or mesh)!");
- return;
- }
- else if (entities.size() == 1)
- {
- alignedEntities = entities;
- }
- else
- {
- std::vector<int> indexes;
- if (!ccEntitySelectionDialog::SelectEntities(entities, indexes, this, tr("Select to-be-aligned entities")))
- {
- //process cancelled by the user
- return;
- }
- //add the selected indexes as 'aligned' entities
- alignedEntities.reserve(indexes.size());
- for (size_t i = 0; i < indexes.size(); ++i)
- {
- alignedEntities.push_back(entities[indexes[i]]);
- }
- //add the others as 'reference' entities
- assert(indexes.size() <= entities.size());
- refEntities.reserve(entities.size() - indexes.size());
- for (size_t i = 0; i < entities.size(); ++i)
- {
- if (std::find(indexes.begin(), indexes.end(), i) == indexes.end())
- {
- refEntities.push_back(entities[i]);
- }
- }
- }
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Error(tr("Not enough memory"));
- return;
- }
- if (alignedEntities.empty())
- {
- ccLog::Error(tr("No to-be-aligned entity selected"));
- return;
- }
- //deselect all entities
- if (m_ccRoot)
- {
- m_ccRoot->unselectAllEntities();
- }
- ccGLWindowInterface* win = new3DView();
- if (!win)
- {
- ccLog::Error(tr("[PointPairRegistration] Failed to create dedicated 3D view!"));
- return;
- }
- //we disable all windows
- disableAllBut(win);
- if (!m_pprDlg)
- {
- m_pprDlg = new ccPointPairRegistrationDlg(m_pickingHub, this, this);
- connect(m_pprDlg, &ccOverlayDialog::processFinished, this, &MainWindow::deactivateRegisterPointPairTool);
- registerOverlayDialog(m_pprDlg, Qt::TopRightCorner);
- }
- if (!m_pprDlg->init(win, alignedEntities, &refEntities))
- {
- deactivateRegisterPointPairTool(false);
- }
- freezeUI(true);
- if (!m_pprDlg->start())
- deactivateRegisterPointPairTool(false);
- else
- updateOverlayDialogsPlacement();
- }
- void MainWindow::deactivateRegisterPointPairTool(bool state)
- {
- if (m_pprDlg)
- m_pprDlg->clear();
- //we enable all GL windows
- enableAll();
- QList<QMdiSubWindow*> subWindowList = m_mdiArea->subWindowList();
- if (!subWindowList.isEmpty())
- subWindowList.first()->showMaximized();
- freezeUI(false);
- updateUI();
- setGlobalZoom();
- }
- void MainWindow::activateSectionExtractionMode()
- {
- if (!haveSelection())
- return;
- if (!m_seTool)
- {
- m_seTool = new ccSectionExtractionTool(this);
- connect(m_seTool, &ccOverlayDialog::processFinished, this, &MainWindow::deactivateSectionExtractionMode);
- registerOverlayDialog(m_seTool, Qt::TopRightCorner);
- }
- //add clouds
- ccGLWindowInterface* firstDisplay = nullptr;
- {
- unsigned validCount = 0;
- for (ccHObject *entity : getSelectedEntities())
- {
- if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- if (m_seTool->addCloud(static_cast<ccGenericPointCloud*>(entity)))
- {
- if (!firstDisplay && entity->getDisplay())
- {
- firstDisplay = static_cast<ccGLWindowInterface*>(entity->getDisplay());
- }
-
- ++validCount;
- }
- }
- }
- if (validCount == 0)
- {
- ccConsole::Error(tr("No cloud in selection!"));
- return;
- }
- }
- //deselect all entities
- if (m_ccRoot)
- {
- m_ccRoot->unselectAllEntities();
- }
- ccGLWindowInterface* win = new3DViewInternal(false);
- if (!win)
- {
- ccLog::Error(tr("[SectionExtraction] Failed to create dedicated 3D view!"));
- return;
- }
- if (firstDisplay && firstDisplay->getGlFilter())
- {
- win->setGlFilter(firstDisplay->getGlFilter()->clone());
- }
- m_seTool->linkWith(win);
- freezeUI(true);
- m_UI->toolBarView->setDisabled(true);
- //we disable all other windows
- disableAllBut(win);
- if (!m_seTool->start())
- deactivateSectionExtractionMode(false);
- else
- updateOverlayDialogsPlacement();
- }
- void MainWindow::deactivateSectionExtractionMode(bool state)
- {
- if (m_seTool)
- m_seTool->removeAllEntities();
- //we enable all GL windows
- enableAll();
- QList<QMdiSubWindow*> subWindowList = m_mdiArea->subWindowList();
- if (!subWindowList.isEmpty())
- subWindowList[0]->showMaximized();
- freezeUI(false);
- m_UI->toolBarView->setDisabled(false);
- updateUI();
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- win->redraw();
- }
- void MainWindow::activateSegmentationMode()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- return;
- if (!haveSelection())
- return;
- if (!m_gsTool)
- {
- m_gsTool = new ccGraphicalSegmentationTool(this);
- connect(m_gsTool, &ccOverlayDialog::processFinished, this, &MainWindow::deactivateSegmentationMode);
- registerOverlayDialog(m_gsTool, Qt::TopRightCorner);
- }
- m_gsTool->linkWith(win);
- for ( ccHObject *entity : getSelectedEntities() )
- {
- m_gsTool->addEntity(entity);
- }
- if (m_gsTool->getNumberOfValidEntities() == 0)
- {
- ccConsole::Error(tr("No segmentable entity in active window!"));
- return;
- }
- freezeUI(true);
- m_UI->toolBarView->setDisabled(false);
- //we disable all other windows
- disableAllBut(win);
- if (!m_gsTool->start())
- deactivateSegmentationMode(false);
- else
- updateOverlayDialogsPlacement();
- }
- void MainWindow::deactivateSegmentationMode(bool state)
- {
- if (!m_gsTool)
- {
- assert(false);
- return;
- }
- bool deleteHiddenParts = false;
- //shall we apply segmentation?
- if (state)
- {
- ccHObject::Container result;
- m_gsTool->applySegmentation(this, result);
- if (m_ccRoot)
- {
- m_ccRoot->selectEntities(result);
- }
- }
- else
- {
- m_gsTool->removeAllEntities();
- }
- //we enable all GL windows
- enableAll();
- freezeUI(false);
- updateUI();
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->redraw();
- }
- }
- void MainWindow::activateTracePolylineMode()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- {
- return;
- }
- if (!m_tplTool)
- {
- m_tplTool = new ccTracePolylineTool(m_pickingHub, this);
- connect(m_tplTool, &ccOverlayDialog::processFinished, this, &MainWindow::deactivateTracePolylineMode);
- registerOverlayDialog(m_tplTool, Qt::TopRightCorner);
- }
- m_tplTool->linkWith(win);
- freezeUI(true);
- m_UI->toolBarView->setDisabled(false);
- //we disable all other windows
- disableAllBut(win);
- if (!m_tplTool->start())
- deactivateTracePolylineMode(false);
- else
- updateOverlayDialogsPlacement();
- }
- void MainWindow::deactivateTracePolylineMode(bool)
- {
- //we enable all GL windows
- enableAll();
- freezeUI(false);
- updateUI();
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->redraw();
- }
- }
- void MainWindow::activatePointListPickingMode()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- return;
- //there should be only one point cloud in current selection!
- if (!haveOneSelection())
- {
- ccConsole::Error(tr("Select one and only one entity!"));
- return;
- }
- ccHObject* entity = m_selectedEntities.front();
- if (!entity->isKindOf(CC_TYPES::POINT_CLOUD) && !entity->isKindOf(CC_TYPES::MESH))
- {
- ccConsole::Error(tr("Select a cloud or a mesh"));
- return;
- }
- if (!entity->isVisible() || !entity->isEnabled())
- {
- ccConsole::Error(tr("Entity must be visible!"));
- return;
- }
- if (!m_plpDlg)
- {
- m_plpDlg = new ccPointListPickingDlg(m_pickingHub, this);
- connect(m_plpDlg, &ccOverlayDialog::processFinished, this, &MainWindow::deactivatePointListPickingMode);
- registerOverlayDialog(m_plpDlg, Qt::TopRightCorner);
- }
- //DGM: we must update marker size spin box value (as it may have changed by the user with the "display dialog")
- m_plpDlg->markerSizeSpinBox->setValue(win->getDisplayParameters().labelMarkerSize);
- m_plpDlg->linkWith(win);
- m_plpDlg->linkWithEntity(entity);
- freezeUI(true);
- //we disable all other windows
- disableAllBut(win);
- if (!m_plpDlg->start())
- deactivatePointListPickingMode(false);
- else
- updateOverlayDialogsPlacement();
- }
- void MainWindow::deactivatePointListPickingMode(bool state)
- {
- if (m_plpDlg)
- {
- m_plpDlg->linkWithEntity(nullptr);
- }
- //we enable all GL windows
- enableAll();
- freezeUI(false);
- updateUI();
- }
- void MainWindow::activatePointPickingMode()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- {
- return;
- }
- if (m_ccRoot)
- {
- m_ccRoot->unselectAllEntities(); //we don't want any entity selected (especially existing labels!)
- }
- if (!m_ppDlg)
- {
- m_ppDlg = new ccPointPropertiesDlg(m_pickingHub, this);
- connect(m_ppDlg, &ccOverlayDialog::processFinished, this, &MainWindow::deactivatePointPickingMode);
- connect(m_ppDlg, &ccPointPropertiesDlg::newLabel, this, &MainWindow::handleNewLabel);
- registerOverlayDialog(m_ppDlg, Qt::TopRightCorner);
- }
- m_ppDlg->linkWith(win);
- freezeUI(true);
- //we disable all other windows
- disableAllBut(win);
- if (!m_ppDlg->start())
- deactivatePointPickingMode(false);
- else
- updateOverlayDialogsPlacement();
- }
- void MainWindow::deactivatePointPickingMode(bool state)
- {
- //if (m_ppDlg)
- // m_ppDlg->linkWith(nullptr);
- //we enable all GL windows
- enableAll();
- freezeUI(false);
- updateUI();
- redrawAll();
- }
- void MainWindow::activateClippingBoxMode()
- {
- if ( !haveSelection() )
- {
- return;
- }
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- {
- return;
- }
- if (!m_clipTool)
- {
- m_clipTool = new ccClippingBoxTool(this);
- connect(m_clipTool, &ccOverlayDialog::processFinished, this, &MainWindow::deactivateClippingBoxMode);
- }
- m_clipTool->linkWith(win);
- ccHObject::Container selectedEntities = getSelectedEntities(); //we have to use a local copy: 'unselectEntity' will change the set of currently selected entities!
- for (ccHObject *entity : selectedEntities)
- {
- if (m_clipTool->addAssociatedEntity(entity))
- {
- //automatically deselect the entity (to avoid seeing its bounding box ;)
- m_ccRoot->unselectEntity(entity);
- }
- }
- if (m_clipTool->getNumberOfAssociatedEntity() == 0)
- {
- m_clipTool->close();
- return;
- }
- if (m_clipTool->start())
- {
- registerOverlayDialog(m_clipTool, Qt::TopRightCorner);
- freezeUI(true);
- updateOverlayDialogsPlacement();
- //deactivate all other GL windows
- disableAllBut(win);
- }
- else
- {
- ccConsole::Error(tr("Unexpected error!")); //indeed...
- }
- }
- void MainWindow::deactivateClippingBoxMode(bool state)
- {
- //we reactivate all GL windows
- enableAll();
- freezeUI(false);
- updateUI();
- }
- void MainWindow::activateTranslateRotateMode()
- {
- if (!haveSelection())
- return;
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- return;
- if (!m_transTool)
- m_transTool = new ccGraphicalTransformationTool(this);
- assert(m_transTool->getNumberOfValidEntities() == 0);
- m_transTool->linkWith(win);
- bool rejectedEntities = false;
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (!m_transTool->addEntity(entity))
- rejectedEntities = true;
- }
- if (m_transTool->getNumberOfValidEntities() == 0)
- {
- ccConsole::Error(tr("No entity eligible for manual transformation! (see console)"));
- return;
- }
- else if (rejectedEntities)
- {
- ccConsole::Error(tr("Some entities were ignored! (see console)"));
- }
- //try to activate "moving mode" in current GL window
- if (m_transTool->start())
- {
- connect(m_transTool, &ccOverlayDialog::processFinished, this, &MainWindow::deactivateTranslateRotateMode);
- registerOverlayDialog(m_transTool, Qt::TopRightCorner);
- freezeUI(true);
- updateOverlayDialogsPlacement();
- //deactivate all other GL windows
- disableAllBut(win);
- }
- else
- {
- ccConsole::Error(tr("Unexpected error!")); //indeed...
- }
- }
- void MainWindow::deactivateTranslateRotateMode(bool state)
- {
- if (m_transTool)
- {
- //reselect previously selected entities!
- if (state && m_ccRoot)
- {
- const ccHObject& transformedSet = m_transTool->getValidEntities();
- try
- {
- ccHObject::Container transformedEntities;
- transformedEntities.resize(transformedSet.getChildrenNumber());
- for (unsigned i = 0; i < transformedSet.getChildrenNumber(); ++i)
- {
- transformedEntities[i] = transformedSet.getChild(i);
- }
- m_ccRoot->selectEntities(transformedEntities);
- }
- catch (const std::bad_alloc&)
- {
- //not enough memory (nothing to do)
- }
- }
- //m_transTool->close();
- }
- //we reactivate all GL windows
- enableAll();
- freezeUI(false);
- updateUI();
- }
- void MainWindow::testFrameRate()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- win->startFrameRateTest();
- }
- void MainWindow::showDisplaySettings()
- {
- ccDisplaySettingsDlg displayOptionsDlg(this);
- connect(&displayOptionsDlg, &ccDisplaySettingsDlg::aspectHasChanged, this, [=] () { redrawAll(); });
-
- displayOptionsDlg.exec();
- disconnect(&displayOptionsDlg);
- }
- void MainWindow::doActionRenderToFile()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- return;
- ccRenderToFileDlg rtfDlg(win->glWidth(), win->glHeight(), this);
- if (rtfDlg.exec())
- {
- QApplication::processEvents();
- win->renderToFile(rtfDlg.getFilename(), rtfDlg.getZoom(), rtfDlg.dontScalePoints(), rtfDlg.renderOverlayItems());
- }
- }
- void MainWindow::doActionEditCamera()
- {
- //current active MDI area
- QMdiSubWindow* qWin = m_mdiArea->activeSubWindow();
- if (!qWin)
- return;
- if (!m_cpeDlg)
- {
- m_cpeDlg = new ccCameraParamEditDlg(qWin, m_pickingHub);
- //m_cpeDlg->makeFrameless(); //does not work on linux
- connect(m_mdiArea, &QMdiArea::subWindowActivated,
- m_cpeDlg, qOverload<QMdiSubWindow*>(&ccCameraParamEditDlg::linkWith));
- registerOverlayDialog(m_cpeDlg, Qt::BottomLeftCorner);
- }
- m_cpeDlg->linkWith(qWin);
- m_cpeDlg->start();
- updateOverlayDialogsPlacement();
- }
- void MainWindow::doActionAdjustZoom()
- {
- //current active MDI area
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- return;
- const ccViewportParameters& params = win->getViewportParameters();
- if (params.perspectiveView)
- {
- ccConsole::Error(tr("Orthographic mode only!"));
- return;
- }
- ccAdjustZoomDlg azDlg(win, this);
- if (!azDlg.exec())
- return;
- //apply new focal
- double focalDist = azDlg.getFocalDistance();
- win->setFocalDistance(focalDist);
- win->redraw();
- }
- static unsigned s_viewportIndex = 0;
- void MainWindow::doActionSaveViewportAsCamera()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- return;
- cc2DViewportObject* viewportObject = new cc2DViewportObject(QString("Viewport #%1").arg(++s_viewportIndex));
- viewportObject->setParameters(win->getViewportParameters());
- viewportObject->setDisplay(win);
- // Save the custom light position as well
- {
- bool customLightEnabled = win->customLightEnabled();
- CCVector3f customLightPos = win->getCustomLightPosition();
- viewportObject->setMetaData("CustomLightEnabled", customLightEnabled);
- viewportObject->setMetaData("CustomLightPosX", customLightPos.x);
- viewportObject->setMetaData("CustomLightPosY", customLightPos.y);
- viewportObject->setMetaData("CustomLightPosZ", customLightPos.z);
- }
- addToDB(viewportObject);
- }
- void MainWindow::zoomOnSelectedEntities()
- {
- ccGLWindowInterface* win = nullptr;
- ccHObject tempGroup("TempGroup");
- size_t selNum = m_selectedEntities.size();
- for (size_t i = 0; i < selNum; ++i)
- {
- ccHObject *entity = m_selectedEntities[i];
-
- if (i == 0 || !win)
- {
- //take the first valid window as reference
- win = static_cast<ccGLWindowInterface*>(entity->getDisplay());
- }
- if (win)
- {
- if (entity->getDisplay() == win)
- {
- tempGroup.addChild(entity,ccHObject::DP_NONE);
- }
- else if (entity->getDisplay() != nullptr)
- {
- ccLog::Error(tr("All selected entities must be displayed in the same 3D view!"));
- return;
- }
- }
- }
- if (tempGroup.getChildrenNumber() != 0)
- {
- ccBBox box = tempGroup.getDisplayBB_recursive(false, win);
- if (!box.isValid())
- {
- ccLog::Warning(tr("Selected entities have no valid bounding-box!"));
- }
- else
- {
- if ( win != nullptr )
- {
- win->updateConstellationCenterAndZoom(&box);
- }
- }
- }
- refreshAll();
- }
- void MainWindow::setGlobalZoom()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- win->zoomGlobal();
- }
- void MainWindow::setPivotAlwaysOn()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->setPivotVisibility(ccGLWindowInterface::PIVOT_ALWAYS_SHOW);
- win->redraw();
- //update pop-up menu 'top' icon
- if (m_pivotVisibilityPopupButton)
- m_pivotVisibilityPopupButton->setIcon(m_UI->actionSetPivotAlwaysOn->icon());
- }
- }
- void MainWindow::setPivotRotationOnly()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->setPivotVisibility(ccGLWindowInterface::PIVOT_SHOW_ON_MOVE);
- win->redraw();
- //update pop-up menu 'top' icon
- if (m_pivotVisibilityPopupButton)
- m_pivotVisibilityPopupButton->setIcon(m_UI->actionSetPivotRotationOnly->icon());
- }
- }
- void MainWindow::setPivotOff()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->setPivotVisibility(ccGLWindowInterface::PIVOT_HIDE);
- win->redraw();
- //update pop-up menu 'top' icon
- if (m_pivotVisibilityPopupButton)
- m_pivotVisibilityPopupButton->setIcon(m_UI->actionSetPivotOff->icon());
- }
- }
- void MainWindow::setOrthoView(ccGLWindowInterface* win)
- {
- if (win)
- {
- if (!checkStereoMode(win))
- {
- return;
- }
- win->setPerspectiveState(false, true);
- win->redraw();
- //update pop-up menu 'top' icon
- if (m_viewModePopupButton)
- m_viewModePopupButton->setIcon(m_UI->actionSetOrthoView->icon());
- if (m_pivotVisibilityPopupButton)
- m_pivotVisibilityPopupButton->setEnabled(true);
- }
- }
- void MainWindow::setCenteredPerspectiveView(ccGLWindowInterface* win, bool autoRedraw/*=true*/)
- {
- if (win)
- {
- win->setPerspectiveState(true, true);
- if (autoRedraw)
- win->redraw();
- //update pop-up menu 'top' icon
- if (m_viewModePopupButton)
- m_viewModePopupButton->setIcon(m_UI->actionSetCenteredPerspectiveView->icon());
- if (m_pivotVisibilityPopupButton)
- m_pivotVisibilityPopupButton->setEnabled(true);
- }
- }
- void MainWindow::setViewerPerspectiveView(ccGLWindowInterface* win)
- {
- if (win)
- {
- win->setPerspectiveState(true,false);
- win->redraw();
- //update pop-up menu 'top' icon
- if (m_viewModePopupButton)
- m_viewModePopupButton->setIcon(m_UI->actionSetViewerPerspectiveView->icon());
- if (m_pivotVisibilityPopupButton)
- m_pivotVisibilityPopupButton->setEnabled(false);
- }
- }
- void MainWindow::enablePickingOperation(ccGLWindowInterface* win, QString message)
- {
- if (!win)
- {
- assert(false);
- return;
- }
- assert(m_pickingHub);
- if (!m_pickingHub->addListener(this))
- {
- ccLog::Error(tr("Can't start the picking mechanism (another tool is already using it)"));
- return;
- }
- //specific case: we prevent the 'point-pair based alignment' tool to process the picked point!
- //if (m_pprDlg)
- // m_pprDlg->pause(true);
- s_pickingWindow = win;
- win->displayNewMessage(message, ccGLWindowInterface::LOWER_LEFT_MESSAGE, true, 24 * 3600);
- win->redraw(true, false);
- freezeUI(true);
- }
- void MainWindow::cancelPreviousPickingOperation(bool aborted)
- {
- if (!s_pickingWindow)
- return;
- switch(s_currentPickingOperation)
- {
- case PICKING_ROTATION_CENTER:
- //nothing to do
- break;
- case PICKING_LEVEL_POINTS:
- if (s_levelMarkersCloud)
- {
- s_pickingWindow->removeFromOwnDB(s_levelMarkersCloud);
- delete s_levelMarkersCloud;
- s_levelMarkersCloud = nullptr;
- }
- break;
- default:
- assert(false);
- break;
- }
- if (aborted)
- {
- s_pickingWindow->displayNewMessage(QString(), ccGLWindowInterface::LOWER_LEFT_MESSAGE); //clear previous messages
- s_pickingWindow->displayNewMessage(tr("Picking operation aborted"), ccGLWindowInterface::LOWER_LEFT_MESSAGE);
- }
- s_pickingWindow->redraw(false);
- //specific case: we allow the 'point-pair based alignment' tool to process the picked point!
- if (m_pprDlg)
- m_pprDlg->pause(false);
- freezeUI(false);
- m_pickingHub->removeListener(this);
- s_pickingWindow = nullptr;
- s_currentPickingOperation = NO_PICKING_OPERATION;
- }
- void MainWindow::onItemPicked(const PickedItem& pi)
- {
- if (!s_pickingWindow || !m_pickingHub)
- {
- return;
- }
- if (!pi.entity)
- {
- return;
- }
- if (m_pickingHub->activeWindow() != s_pickingWindow)
- {
- ccLog::Warning(tr("The point was picked in the wrong window"));
- return;
- }
- CCVector3 pickedPoint = pi.P3D;
- switch(s_currentPickingOperation)
- {
- case PICKING_LEVEL_POINTS:
- {
- //we only accept points picked on the right entity!
- //if (obj != s_levelEntity)
- //{
- // ccLog::Warning(tr("[Level] Only points picked on '%1' are considered!").arg(s_levelEntity->getName()));
- // return;
- //}
- if (!s_levelMarkersCloud)
- {
- assert(false);
- cancelPreviousPickingOperation(true);
- }
- for (unsigned i = 0; i < s_levelMarkersCloud->size(); ++i)
- {
- const CCVector3* P = s_levelMarkersCloud->getPoint(i);
- if ((pickedPoint - *P).norm() < 1.0e-6)
- {
- ccLog::Warning(tr("[Level] Point is too close from the others!"));
- return;
- }
- }
- //add the corresponding marker
- s_levelMarkersCloud->addPoint(pickedPoint);
- unsigned markerCount = s_levelMarkersCloud->size();
- cc2DLabel* label = new cc2DLabel();
- label->addPickedPoint(s_levelMarkersCloud, markerCount - 1);
- label->setName(QString("P#%1").arg(markerCount));
- label->setDisplayedIn2D(false);
- label->setDisplay(s_pickingWindow);
- label->setVisible(true);
- s_levelMarkersCloud->addChild(label);
- s_pickingWindow->redraw();
- if (markerCount == 3)
- {
- //we have enough points!
- const CCVector3* A = s_levelMarkersCloud->getPoint(0);
- const CCVector3* B = s_levelMarkersCloud->getPoint(1);
- const CCVector3* C = s_levelMarkersCloud->getPoint(2);
- CCVector3 X = *B - *A;
- CCVector3 Y = *C - *A;
- CCVector3 Z = X.cross(Y);
- //we choose 'Z' so that it points 'upward' relatively to the camera (assuming the user will be looking from the top)
- CCVector3d viewDir = s_pickingWindow->getViewportParameters().getViewDir();
- if (Z.toDouble().dot(viewDir) > 0)
- {
- Z = -Z;
- }
- Y = Z.cross(X);
- X.normalize();
- Y.normalize();
- Z.normalize();
- ccGLMatrixd trans;
- {
- double* mat = trans.data();
- mat[0] = X.x; mat[4] = X.y; mat[8] = X.z; mat[12] = 0.0;
- mat[1] = Y.x; mat[5] = Y.y; mat[9] = Y.z; mat[13] = 0.0;
- mat[2] = Z.x; mat[6] = Z.y; mat[10] = Z.z; mat[14] = 0.0;
- mat[3] = 0.0; mat[7] = 0.0; mat[11] = 0.0; mat[15] = 1.0;
- }
- CCVector3d T = -A->toDouble();
- trans.apply(T);
- T += *A;
- trans.setTranslation(T);
- assert(haveOneSelection() && m_selectedEntities.front() == s_levelEntity);
- applyTransformation(trans, false);
- //clear message
- s_pickingWindow->displayNewMessage(QString(), ccGLWindowInterface::LOWER_LEFT_MESSAGE, false); //clear previous message
- s_pickingWindow->setView(CC_TOP_VIEW);
- }
- else
- {
- //we need more points!
- return;
- }
- }
- //we use the next 'case' entry (PICKING_ROTATION_CENTER) to redefine the rotation center as well!
- assert(s_levelMarkersCloud && s_levelMarkersCloud->size() != 0);
- pickedPoint = *s_levelMarkersCloud->getPoint(0);
- //break;
- case PICKING_ROTATION_CENTER:
- {
- CCVector3d newPivot = pickedPoint;
- //specific case: transformation tool is enabled
- if (m_transTool && m_transTool->started())
- {
- m_transTool->setRotationCenter(newPivot);
- const unsigned& precision = s_pickingWindow->getDisplayParameters().displayedNumPrecision;
- s_pickingWindow->displayNewMessage(QString(), ccGLWindowInterface::LOWER_LEFT_MESSAGE, false); //clear previous message
- s_pickingWindow->displayNewMessage(QString("Point (%1 ; %2 ; %3) set as rotation center for interactive transformation")
- .arg(pickedPoint.x, 0, 'f', precision)
- .arg(pickedPoint.y, 0, 'f', precision)
- .arg(pickedPoint.z, 0, 'f', precision),
- ccGLWindowInterface::LOWER_LEFT_MESSAGE, true);
- }
- else
- {
- const ccViewportParameters& params = s_pickingWindow->getViewportParameters();
- if (!params.perspectiveView || params.objectCenteredView)
- {
- //apply current GL transformation (if any)
- pi.entity->getGLTransformation().apply(newPivot);
- s_pickingWindow->setPivotPoint(newPivot, true, true);
- }
- }
- //s_pickingWindow->redraw(); //already called by 'cancelPreviousPickingOperation' (see below)
- }
- break;
- default:
- assert(false);
- break;
- }
- cancelPreviousPickingOperation(false);
- }
- void MainWindow::doLevel()
- {
- //picking operation already in progress
- if (s_pickingWindow)
- {
- if (s_currentPickingOperation == PICKING_LEVEL_POINTS)
- {
- cancelPreviousPickingOperation(true);
- }
- else
- {
- ccConsole::Error(tr("Stop the other picking operation first!"));
- }
- return;
- }
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- {
- ccConsole::Error(tr("No active 3D view!"));
- return;
- }
- if (!haveOneSelection())
- {
- ccConsole::Error(tr("Select an entity!"));
- return;
- }
- //create markers cloud
- assert(!s_levelMarkersCloud);
- {
- s_levelMarkersCloud = new ccPointCloud("Level points");
- if (!s_levelMarkersCloud->reserve(3))
- {
- ccConsole::Error(tr("Not enough memory!"));
- return;
- }
- win->addToOwnDB(s_levelMarkersCloud);
- }
- s_levelEntity = m_selectedEntities.front();
- s_levelLabels.clear();
- s_currentPickingOperation = PICKING_LEVEL_POINTS;
- enablePickingOperation(win, tr("Pick three points on the floor plane (click the Level button or press Escape to cancel)"));
- }
- void MainWindow::doPickRotationCenter()
- {
- //picking operation already in progress
- if (s_pickingWindow)
- {
- if (s_currentPickingOperation == PICKING_ROTATION_CENTER)
- {
- cancelPreviousPickingOperation(true);
- }
- else
- {
- ccConsole::Error(tr("Stop the other picking operation first!"));
- }
- return;
- }
- ccGLWindowInterface* win = getActiveGLWindow();
- if (!win)
- {
- ccConsole::Error(tr("No active 3D view!"));
- return;
- }
- bool objectCentered = true;
- bool perspectiveEnabled = win->getPerspectiveState(objectCentered);
- if (perspectiveEnabled && !objectCentered)
- {
- ccLog::Error(tr("Perspective mode is viewer-centered: can't use a point as rotation center!"));
- return;
- }
- s_currentPickingOperation = PICKING_ROTATION_CENTER;
- enablePickingOperation(win, tr("Pick a point to be used as rotation center (click on icon again to cancel)"));
- }
- ccPointCloud* MainWindow::askUserToSelectACloud(ccHObject* defaultCloudEntity/*=nullptr*/, QString inviteMessage/*=QString()*/)
- {
- ccHObject::Container clouds;
- m_ccRoot->getRootEntity()->filterChildren(clouds, true, CC_TYPES::POINT_CLOUD, true);
- if (clouds.empty())
- {
- ccConsole::Error(tr("No cloud in database!"));
- return nullptr;
- }
- //default selected index
- int selectedIndex = 0;
- if (defaultCloudEntity)
- {
- for (size_t i = 1; i < clouds.size(); ++i)
- {
- if (clouds[i] == defaultCloudEntity)
- {
- selectedIndex = static_cast<int>(i);
- break;
- }
- }
- }
- //ask the user to choose a cloud
- {
- selectedIndex = ccItemSelectionDlg::SelectEntity(clouds, selectedIndex, this, inviteMessage);
- if (selectedIndex < 0)
- return nullptr;
- }
- assert(selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < clouds.size());
- return ccHObjectCaster::ToPointCloud(clouds[selectedIndex]);
- }
- void MainWindow::toggleSelectedEntitiesProperty( ccEntityAction::TOGGLE_PROPERTY property )
- {
- if ( !ccEntityAction::toggleProperty( m_selectedEntities, property ) )
- {
- return;
- }
-
- refreshAll();
- updateUI();
- }
- void MainWindow::clearSelectedEntitiesProperty( ccEntityAction::CLEAR_PROPERTY property )
- {
- if ( !ccEntityAction::clearProperty( m_selectedEntities, property, this ) )
- {
- return;
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::setView( CC_VIEW_ORIENTATION view )
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->setView(view);
- }
- }
- void MainWindow::spawnHistogramDialog(const std::vector<unsigned>& histoValues, double minVal, double maxVal, QString title, QString xAxisLabel)
- {
- ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(this);
- hDlg->setAttribute(Qt::WA_DeleteOnClose, true);
- hDlg->setWindowTitle(tr("Histogram"));
- ccHistogramWindow* histogram = hDlg->window();
- {
- histogram->setTitle(title);
- histogram->fromBinArray(histoValues, minVal, maxVal);
- histogram->setAxisLabels(xAxisLabel, tr("Count"));
- histogram->refresh();
- }
- hDlg->show();
- }
- void MainWindow::showSelectedEntitiesHistogram()
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- //for "real" point clouds only
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud( entity );
- if (cloud)
- {
- //we display the histogram of the current scalar field
- ccScalarField* sf = static_cast<ccScalarField*>(cloud->getCurrentDisplayedScalarField());
- if (sf)
- {
- ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(this);
- hDlg->setAttribute(Qt::WA_DeleteOnClose, true);
- hDlg->setWindowTitle(tr("Histogram [%1]").arg(cloud->getName()));
- ccHistogramWindow* histogram = hDlg->window();
- {
- unsigned numberOfPoints = cloud->size();
- unsigned numberOfClasses = static_cast<unsigned>(sqrt(static_cast<double>(numberOfPoints)));
- //we take the 'nearest' multiple of 4
- numberOfClasses &= (~3);
- numberOfClasses = std::max<unsigned>(4, numberOfClasses);
- numberOfClasses = std::min<unsigned>(256, numberOfClasses);
- histogram->setTitle(tr("%1 (%2 values) ").arg(QString::fromStdString(sf->getName())).arg(numberOfPoints));
- bool showNaNValuesInGrey = sf->areNaNValuesShownInGrey();
- histogram->fromSF(sf, numberOfClasses, true, showNaNValuesInGrey);
- histogram->setAxisLabels(QString::fromStdString(sf->getName()), tr("Count"));
- histogram->refresh();
- }
- hDlg->show();
- }
- }
- }
- }
- void MainWindow::doActionCrop()
- {
- //find candidates
- std::vector<ccHObject*> candidates;
- ccBBox baseBB;
- {
- const ccHObject::Container& selectedEntities = getSelectedEntities();
- for ( ccHObject *entity : selectedEntities )
- {
- if ( entity->isA(CC_TYPES::POINT_CLOUD)
- || entity->isKindOf(CC_TYPES::MESH) )
- {
- candidates.push_back(entity);
- baseBB += entity->getOwnBB();
- }
- }
- }
- if (candidates.empty())
- {
- ccConsole::Warning(tr("[Crop] No eligible candidate found!"));
- return;
- }
- ccBoundingBoxEditorDlg bbeDlg(false, false, this);
- bbeDlg.setBaseBBox(baseBB, false);
- bbeDlg.showInclusionWarning(false);
- bbeDlg.setWindowTitle("Crop");
- if (!bbeDlg.exec())
- {
- //process cancelled by user
- return;
- }
- //deselect all entities
- if (m_ccRoot)
- {
- m_ccRoot->unselectAllEntities();
- }
- //cropping box
- ccBBox box = bbeDlg.getBox();
- //process cloud/meshes
- bool errors = false;
- bool successes = false;
- {
- for ( ccHObject *entity : candidates )
- {
- ccHObject* croppedEnt = ccCropTool::Crop(entity, box, true);
- if (croppedEnt)
- {
- croppedEnt->setName(entity->getName() + QString(".cropped"));
- croppedEnt->setDisplay(entity->getDisplay());
- croppedEnt->prepareDisplayForRefresh();
- if (entity->getParent())
- entity->getParent()->addChild(croppedEnt);
- entity->setEnabled(false);
- addToDB(croppedEnt);
- //select output entity
- m_ccRoot->selectEntity(croppedEnt, true);
- successes = true;
- }
- else
- {
- errors = true;
- }
- }
- }
- if (successes)
- ccLog::Warning(tr("[Crop] Selected entities have been hidden"));
- if (errors)
- ccLog::Error(tr("Error(s) occurred! See the Console"));
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionClone()
- {
- ccHObject* lastClone = nullptr;
-
- for ( ccHObject *entity : getSelectedEntities() )
- {
- ccHObject* clone = nullptr;
-
- if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- clone = ccHObjectCaster::ToGenericPointCloud(entity)->clone();
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning cloud %1").arg(entity->getName()));
- }
- }
- else if (entity->isKindOf(CC_TYPES::PRIMITIVE))
- {
- clone = static_cast<ccGenericPrimitive*>(entity)->clone();
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning primitive %1").arg(entity->getName()));
- }
- }
- else if (entity->isA(CC_TYPES::MESH))
- {
- clone = ccHObjectCaster::ToMesh(entity)->cloneMesh();
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning mesh %1").arg(entity->getName()));
- }
- }
- else if (entity->isA(CC_TYPES::POLY_LINE))
- {
- clone = ccHObjectCaster::ToPolyline(entity)->clone();
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning polyline %1").arg(entity->getName()));
- }
- }
- else if (entity->isA(CC_TYPES::FACET))
- {
- clone = ccHObjectCaster::ToFacet(entity);
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning facet %1").arg(entity->getName()));
- }
- }
- else if (entity->isA(CC_TYPES::CAMERA_SENSOR))
- {
- ccCameraSensor* camera = ccHObjectCaster::ToCameraSensor(entity);
- if (camera)
- {
- clone = new ccCameraSensor(*camera);
- if (camera->getParent())
- {
- camera->getParent()->addChild(clone);
- }
- }
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning camera sensor %1").arg(entity->getName()));
- }
- }
- else if (entity->isA(CC_TYPES::GBL_SENSOR))
- {
- ccGBLSensor* sensor = ccHObjectCaster::ToGBLSensor(entity);
- if (sensor)
- {
- clone = new ccGBLSensor(*sensor);
- if (sensor->getParent())
- {
- sensor->getParent()->addChild(clone);
- }
- }
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning GBL sensor %1").arg(entity->getName()));
- }
- }
- else if (entity->isA(CC_TYPES::IMAGE))
- {
- ccImage* image = ccHObjectCaster::ToImage(entity);
- if (image)
- {
- clone = new ccImage(*image);
- if (image->getParent())
- {
- image->getParent()->addChild(clone);
- }
- }
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning image %1").arg(entity->getName()));
- }
- }
- else if (entity->isA(CC_TYPES::LABEL_2D))
- {
- cc2DLabel* label = ccHObjectCaster::To2DLabel(entity);
- if (label)
- {
- clone = new cc2DLabel(*label, true);
- if (label->getParent())
- {
- label->getParent()->addChild(clone);
- }
- }
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning label %1").arg(entity->getName()));
- }
- }
- else if (entity->isA(CC_TYPES::VIEWPORT_2D_OBJECT))
- {
- cc2DViewportObject* viewport = ccHObjectCaster::To2DViewportObject(entity);
- if (viewport)
- {
- clone = new cc2DViewportObject(*viewport);
- if (viewport->getParent())
- {
- viewport->getParent()->addChild(clone);
- }
- }
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning viewport %1").arg(entity->getName()));
- }
- }
- else if (entity->isA(CC_TYPES::VIEWPORT_2D_LABEL))
- {
- cc2DViewportLabel* viewportLabel = ccHObjectCaster::To2DViewportLabel(entity);
- if (viewportLabel)
- {
- clone = new cc2DViewportLabel(*viewportLabel);
- if (viewportLabel->getParent())
- {
- viewportLabel->getParent()->addChild(clone);
- }
- }
- if (!clone)
- {
- ccConsole::Error(tr("An error occurred while cloning viewport %1").arg(entity->getName()));
- }
- }
- else
- {
- ccLog::Warning(tr("Entity '%1' can't be cloned (type not supported yet!)").arg(entity->getName()));
- }
- if (clone)
- {
- //copy GL transformation history
- clone->setGLTransformationHistory(entity->getGLTransformationHistory());
- //copy display
- clone->setDisplay(entity->getDisplay());
- addToDB(clone);
- lastClone = clone;
- }
- }
- if (lastClone && m_ccRoot)
- {
- m_ccRoot->selectEntity(lastClone);
- }
- updateUI();
- }
- void MainWindow::doActionAddConstantSF()
- {
- if (!haveOneSelection())
- {
- if (haveSelection())
- ccConsole::Error(tr("Select only one cloud or one mesh!"));
- return;
- }
- ccHObject* ent = m_selectedEntities.front();
- bool lockedVertices;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
- //for "real" point clouds only
- if (!cloud)
- {
- return;
- }
- if (lockedVertices && !ent->isAncestorOf(cloud))
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(),true);
- return;
- }
- QString defaultName = "Constant";
- unsigned trys = 1;
- while (cloud->getScalarFieldIndexByName(defaultName.toStdString()) >= 0 || trys > 99)
- {
- defaultName = tr("Constant #%1").arg(++trys);
- }
- //ask for a name
- bool ok;
- QString sfName = QInputDialog::getText(this, tr("New SF name"), tr("SF name (must be unique)"), QLineEdit::Normal, defaultName, &ok);
- if (!ok)
- return;
- if (ccEntityAction::sfAddConstant(cloud, sfName, false, this))
- {
- updateUI();
- cloud->redrawDisplay();
- }
- }
- void MainWindow::doActionAddClassificationSF()
- {
- if (!haveOneSelection())
- {
- if (haveSelection())
- ccConsole::Error(tr("Select only one cloud or one mesh!"));
- return;
- }
- ccHObject* ent = m_selectedEntities.front();
- bool lockedVertices;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
- //for "real" point clouds only
- if (!cloud)
- {
- return;
- }
- if (lockedVertices && !ent->isAncestorOf(cloud))
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), true);
- return;
- }
- if (ccEntityAction::sfAddConstant(cloud, "Classification", true, this))
- {
- updateUI();
- cloud->redrawDisplay();
- }
- }
- void MainWindow::doActionScalarFieldFromColor()
- {
- if ( !ccEntityAction::sfFromColor(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionScalarFieldArithmetic()
- {
- if ( !ccEntityAction::sfArithmetic(m_selectedEntities, this) )
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionFitSphere()
- {
- double outliersRatio = 0.5;
- double confidence = 0.99;
- ccProgressDialog pDlg(true, this);
- pDlg.setAutoClose(false);
- for ( ccHObject *entity : getSelectedEntities() )
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- if (!cloud)
- continue;
- CCVector3 center;
- PointCoordinateType radius = 0;
- double rms = std::numeric_limits<double>::quiet_NaN();
- if (CCCoreLib::GeometricalAnalysisTools::DetectSphereRobust(cloud,
- outliersRatio,
- center,
- radius,
- rms,
- &pDlg,
- confidence) != CCCoreLib::GeometricalAnalysisTools::NoError)
- {
- ccLog::Warning(tr("[Fit sphere] Failed to fit a sphere on cloud '%1'").arg(cloud->getName()));
- continue;
- }
- ccLog::Print(tr("[Fit sphere] Cloud '%1': center (%2,%3,%4) - radius = %5 [RMS = %6]")
- .arg(cloud->getName())
- .arg(center.x)
- .arg(center.y)
- .arg(center.z)
- .arg(radius)
- .arg(rms));
- ccGLMatrix trans;
- trans.setTranslation(center);
- ccSphere* sphere = new ccSphere(radius, &trans, tr("Sphere r=%1").arg(radius));
- cloud->addChild(sphere);
- //sphere->setDisplay(cloud->getDisplay());
- sphere->prepareDisplayForRefresh();
- sphere->copyGlobalShiftAndScale(*cloud);
- sphere->setMetaData("RMS", rms);
- addToDB(sphere, false, false, false);
- }
- refreshAll();
- }
- void MainWindow::doActionFitCircle()
- {
- ccProgressDialog pDlg(true, this);
- pDlg.setAutoClose(false);
- for (ccHObject* entity : getSelectedEntities())
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- if (!cloud)
- continue;
- CCVector3 center;
- CCVector3 normal;
- PointCoordinateType radius = 0;
- double rms = std::numeric_limits<double>::quiet_NaN();
- if (CCCoreLib::GeometricalAnalysisTools::DetectCircle( cloud,
- center,
- normal,
- radius,
- rms,
- &pDlg) != CCCoreLib::GeometricalAnalysisTools::NoError)
- {
- ccLog::Warning(tr("[Fit circle] Failed to fit a circle on cloud '%1'").arg(cloud->getName()));
- continue;
- }
- ccLog::Print(tr("[Fit circle] Cloud '%1': center (%2,%3,%4) - radius = %5 [RMS = %6]")
- .arg(cloud->getName())
- .arg(center.x)
- .arg(center.y)
- .arg(center.z)
- .arg(radius)
- .arg(rms));
- ccLog::Print(tr("[Fit circle] Normal (%1,%2,%3)")
- .arg(normal.x)
- .arg(normal.y)
- .arg(normal.z));
- // create the circle representation as a polyline
- ccPolyline* circle = ccPolyline::Circle(CCVector3(0, 0, 0), radius, 128);
- if (circle)
- {
- circle->setName(QObject::tr("Circle r=%1").arg(radius));
- cloud->addChild(circle);
- circle->prepareDisplayForRefresh();
- circle->copyGlobalShiftAndScale(*cloud);
- circle->setMetaData("RMS", rms);
- ccGLMatrix trans = ccGLMatrix::FromToRotation(CCVector3(0, 0, 1), normal);
- trans.setTranslation(center);
- circle->applyGLTransformation_recursive(&trans);
- addToDB(circle, false, false, false);
- }
- }
- refreshAll();
- }
- void MainWindow::doActionFitPlane()
- {
- doComputePlaneOrientation(false);
- }
- void MainWindow::doActionFitFacet()
- {
- doComputePlaneOrientation(true);
- }
- void MainWindow::doComputePlaneOrientation(bool fitFacet)
- {
- if (!haveSelection())
- return;
- double maxEdgeLength = 0.0;
- if (fitFacet)
- {
- bool ok = true;
- static double s_polygonMaxEdgeLength = 0.0;
- maxEdgeLength = QInputDialog::getDouble(this, tr("Fit facet"), tr("Max edge length (0 = no limit)"), s_polygonMaxEdgeLength, 0, 1.0e9, 8, &ok);
- if (!ok)
- return;
- s_polygonMaxEdgeLength = maxEdgeLength;
- }
- ccHObject::Container selectedEntities = getSelectedEntities(); //warning, getSelectedEntites may change during this loop!
- bool firstEntity = true;
-
- for (ccHObject *entity : selectedEntities)
- {
- ccShiftedObject* shifted = nullptr;
- CCCoreLib::GenericIndexedCloudPersist* cloud = nullptr;
- if (entity->isKindOf(CC_TYPES::POLY_LINE))
- {
- ccPolyline* poly = ccHObjectCaster::ToPolyline(entity);
- cloud = static_cast<CCCoreLib::GenericIndexedCloudPersist*>(poly);
- shifted = poly;
- }
- else
- {
- ccGenericPointCloud* gencloud = ccHObjectCaster::ToGenericPointCloud(entity);
- if (gencloud)
- {
- cloud = static_cast<CCCoreLib::GenericIndexedCloudPersist*>(gencloud);
- shifted = gencloud;
- }
- }
- if (cloud)
- {
- double rms = 0.0;
- CCVector3 C;
- CCVector3 N;
- ccHObject* plane = nullptr;
- if (fitFacet)
- {
- ccFacet* facet = ccFacet::Create(cloud, static_cast<PointCoordinateType>(maxEdgeLength));
- if (facet)
- {
- plane = static_cast<ccHObject*>(facet);
- N = facet->getNormal();
- C = facet->getCenter();
- rms = facet->getRMS();
- //manually copy shift & scale info!
- if (shifted)
- {
- ccPolyline* contour = facet->getContour();
- if (contour)
- {
- contour->copyGlobalShiftAndScale(*shifted);
- }
- ccMesh* polygon = facet->getPolygon();
- if (polygon)
- {
- polygon->copyGlobalShiftAndScale(*shifted);
- }
- ccPointCloud* points = facet->getOriginPoints();
- if (points)
- {
- points->copyGlobalShiftAndScale(*shifted);
- }
- }
- }
- }
- else
- {
- ccPlane* pPlane = ccPlane::Fit(cloud, &rms);
- if (pPlane)
- {
- plane = static_cast<ccHObject*>(pPlane);
- N = pPlane->getNormal();
- C = *CCCoreLib::Neighbourhood(cloud).getGravityCenter();
- pPlane->enableStippling(true);
- if (shifted)
- {
- pPlane->copyGlobalShiftAndScale(*shifted);
- }
- }
- }
- //as all information appears in Console...
- forceConsoleDisplay();
- if (plane)
- {
- ccConsole::Print(tr("[Orientation] Entity '%1'").arg(entity->getName()));
- ccConsole::Print(tr("\t- plane fitting RMS: %1").arg(rms));
- //We always consider the normal with a positive 'Z' by default!
- if (N.z < 0.0)
- N *= -1.0;
- ccConsole::Print(tr("\t- normal: (%1, %2, %3)").arg(N.x).arg(N.y).arg(N.z));
- //we compute strike & dip by the way
- PointCoordinateType dip = 0.0f;
- PointCoordinateType dipDir = 0.0f;
- ccNormalVectors::ConvertNormalToDipAndDipDir(N, dip, dipDir);
- QString dipAndDipDirStr = ccNormalVectors::ConvertDipAndDipDirToString(dip, dipDir);
- ccConsole::Print(QString("\t- %1").arg(dipAndDipDirStr));
- //hack: output 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);
- ccConsole::Print(tr("[Orientation] A matrix that would make this plane horizontal (normal towards Z+) is:"));
- ccConsole::Print(makeZPosMatrix.toString(12,' ')); //full precision
- ccConsole::Print(tr("[Orientation] You can copy this matrix values (CTRL+C) and paste them in the 'Apply transformation tool' dialog"));
- plane->setName(dipAndDipDirStr);
- plane->applyGLTransformation_recursive(); //not yet in DB
- plane->setVisible(true);
- plane->setSelectionBehavior(ccHObject::SELECTION_FIT_BBOX);
- entity->addChild(plane);
- plane->setDisplay(entity->getDisplay());
- plane->prepareDisplayForRefresh_recursive();
- addToDB(plane);
- if (firstEntity)
- {
- m_ccRoot->unselectAllEntities();
- m_ccRoot->selectEntity(plane);
- }
- }
- else
- {
- ccConsole::Warning(tr("Failed to fit a plane/facet on entity '%1'").arg(entity->getName()));
- }
- }
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doShowPrimitiveFactory()
- {
- if (!m_pfDlg)
- m_pfDlg = new ccPrimitiveFactoryDlg(this);
- m_pfDlg->setModal(false);
- m_pfDlg->setWindowModality(Qt::NonModal);
- m_pfDlg->show();
- }
- void MainWindow::doComputeGeometricFeature()
- {
- static ccLibAlgorithms::GeomCharacteristicSet s_selectedCharacteristics;
- static CCVector3 s_upDir(0, 0, 1);
- static bool s_upDirDefined = false;
- ccGeomFeaturesDlg gfDlg(this);
- double radius = ccLibAlgorithms::GetDefaultCloudKernelSize(m_selectedEntities);
- gfDlg.setRadius(radius);
- // restore semi-persistent parameters
- gfDlg.setSelectedFeatures(s_selectedCharacteristics);
- if (s_upDirDefined)
- {
- gfDlg.setUpDirection(s_upDir);
- }
- if (!gfDlg.exec())
- return;
- radius = gfDlg.getRadius();
- if (!gfDlg.getSelectedFeatures(s_selectedCharacteristics))
- {
- ccLog::Error(tr("Not enough memory!"));
- return;
- }
- CCVector3* upDir = gfDlg.getUpDirection();
- // remember semi-persistent parameters
- s_upDirDefined = (upDir != nullptr);
- if (s_upDirDefined)
- {
- s_upDir = *upDir;
- }
- ccLibAlgorithms::ComputeGeomCharacteristics(s_selectedCharacteristics, static_cast<PointCoordinateType>(radius), m_selectedEntities, upDir, this);
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionSFGradient()
- {
- if (!ccLibAlgorithms::ApplyCCLibAlgorithm(ccLibAlgorithms::CCLIB_ALGO_SF_GRADIENT, m_selectedEntities, this))
- return;
- refreshAll();
- updateUI();
- }
- void MainWindow::doSphericalNeighbourhoodExtractionTest()
- {
- size_t selNum = m_selectedEntities.size();
- if (selNum < 1)
- return;
- //spherical neighborhood extraction radius
- double sphereRadius = ccLibAlgorithms::GetDefaultCloudKernelSize(m_selectedEntities);
- if (sphereRadius < 0)
- {
- ccConsole::Error(tr("Invalid kernel size!"));
- return;
- }
- bool ok;
- double val = QInputDialog::getDouble(this, tr("SNE test"), tr("Radius:"), sphereRadius, DBL_MIN, 1.0e9, 8, &ok);
- if (!ok)
- return;
- sphereRadius = val;
- QString sfName = tr("Spherical extraction test (%1)").arg(sphereRadius);
- ccProgressDialog pDlg(true, this);
- pDlg.setAutoClose(false);
- for (size_t i = 0; i < selNum; ++i)
- {
- //we only process clouds
- if (!m_selectedEntities[i]->isA(CC_TYPES::POINT_CLOUD))
- {
- continue;
- }
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(m_selectedEntities[i]);
- int sfIdx = cloud->getScalarFieldIndexByName(sfName.toStdString());
- if (sfIdx < 0)
- sfIdx = cloud->addScalarField(sfName.toStdString());
- if (sfIdx < 0)
- {
- ccConsole::Error(tr("Failed to create scalar field on cloud '%1' (not enough memory?)").arg(cloud->getName()));
- return;
- }
-
- ccOctree::Shared octree = cloud->getOctree();
- if (!octree)
- {
- pDlg.reset();
- pDlg.show();
- octree = cloud->computeOctree(&pDlg);
- if (!octree)
- {
- ccConsole::Error(tr("Couldn't compute octree for cloud '%1'!").arg(cloud->getName()));
- return;
- }
- }
- CCCoreLib::ScalarField* sf = cloud->getScalarField(sfIdx);
- sf->fill(CCCoreLib::NAN_VALUE);
- cloud->setCurrentScalarField(sfIdx);
- QElapsedTimer eTimer;
- eTimer.start();
- size_t extractedPoints = 0;
- unsigned char level = octree->findBestLevelForAGivenNeighbourhoodSizeExtraction(static_cast<PointCoordinateType>(sphereRadius));
- std::random_device rd; // non-deterministic generator
- std::mt19937 gen(rd()); // to seed mersenne twister.
- std::uniform_int_distribution<unsigned> dist(0, cloud->size() - 1);
- const unsigned samples = 1000;
- for (unsigned j = 0; j < samples; ++j)
- {
- unsigned randIndex = dist(gen);
- CCCoreLib::DgmOctree::NeighboursSet neighbours;
- octree->getPointsInSphericalNeighbourhood( *cloud->getPoint(randIndex),
- static_cast<PointCoordinateType>(sphereRadius),
- neighbours,
- level );
- size_t neihgboursCount = neighbours.size();
- extractedPoints += neihgboursCount;
- for (size_t k = 0; k < neihgboursCount; ++k)
- {
- cloud->setPointScalarValue(neighbours[k].pointIndex, static_cast<ScalarType>(sqrt(neighbours[k].squareDistd)));
- }
- }
- ccConsole::Print(tr("[SNE_TEST] Mean extraction time = %1 ms (radius = %2, mean (neighbours) = %3)").arg(eTimer.elapsed()).arg(sphereRadius).arg(extractedPoints / static_cast<double>(samples), 0, 'f', 1));
- sf->computeMinAndMax();
- cloud->setCurrentDisplayedScalarField(sfIdx);
- cloud->showSF(true);
- cloud->prepareDisplayForRefresh();
- }
- refreshAll();
- updateUI();
- }
- void MainWindow::doCylindricalNeighbourhoodExtractionTest()
- {
- bool ok;
- double radius = QInputDialog::getDouble(this, tr("CNE Test"), tr("radius"), 0.02, 1.0e-6, 1.0e6, 6, &ok);
- if (!ok)
- return;
- double height = QInputDialog::getDouble(this, tr("CNE Test"), tr("height"), 0.05, 1.0e-6, 1.0e6, 6, &ok);
- if (!ok)
- return;
- ccPointCloud* cloud = new ccPointCloud(tr("cube"));
- const unsigned ptsCount = 1000000;
- if (!cloud->reserve(ptsCount))
- {
- ccConsole::Error(tr("Not enough memory!"));
- delete cloud;
- return;
- }
- //fill a unit cube with random points
- {
- std::random_device rd; // non-deterministic generator
- std::mt19937 gen(rd()); // to seed mersenne twister.
- std::uniform_real_distribution<double> dist(0, 1);
- for (unsigned i = 0; i < ptsCount; ++i)
- {
- CCVector3 P(dist(gen),
- dist(gen),
- dist(gen));
- cloud->addPoint(P);
- }
- }
- //get/Add scalar field
- static const char DEFAULT_CNE_TEST_TEMP_SF_NAME[] = "CNE test";
- int sfIdx = cloud->getScalarFieldIndexByName(DEFAULT_CNE_TEST_TEMP_SF_NAME);
- if (sfIdx < 0)
- sfIdx = cloud->addScalarField(DEFAULT_CNE_TEST_TEMP_SF_NAME);
- if (sfIdx < 0)
- {
- ccConsole::Error(tr("Not enough memory!"));
- delete cloud;
- return;
- }
- cloud->setCurrentScalarField(sfIdx);
- //reset scalar field
- cloud->getScalarField(sfIdx)->fill(CCCoreLib::NAN_VALUE);
- ccProgressDialog pDlg(true, this);
- ccOctree::Shared octree = cloud->computeOctree(&pDlg);
- if (octree)
- {
- QElapsedTimer subTimer;
- subTimer.start();
- unsigned long long extractedPoints = 0;
- unsigned char level = octree->findBestLevelForAGivenNeighbourhoodSizeExtraction(static_cast<PointCoordinateType>(2.5*radius)); //2.5 = empirical
- const unsigned samples = 1000;
- std::random_device rd; // non-deterministic generator
- std::mt19937 gen(rd()); // to seed mersenne twister.
- std::uniform_real_distribution<PointCoordinateType> distAngle(0, static_cast<PointCoordinateType>(2 * M_PI));
- std::uniform_int_distribution<unsigned> distIndex(0, ptsCount - 1);
- for (unsigned j = 0; j < samples; ++j)
- {
- //generate random normal vector
- CCVector3 dir(0, 0, 1);
- {
- ccGLMatrix rot;
- rot.initFromParameters(distAngle(gen),
- distAngle(gen),
- distAngle(gen),
- CCVector3(0, 0, 0));
- rot.applyRotation(dir);
- }
- unsigned randIndex = distIndex(gen);
- CCCoreLib::DgmOctree::CylindricalNeighbourhood cn;
- cn.center = *cloud->getPoint(randIndex);
- cn.dir = dir;
- cn.level = level;
- cn.radius = static_cast<PointCoordinateType>(radius);
- cn.maxHalfLength = static_cast<PointCoordinateType>(height / 2);
- octree->getPointsInCylindricalNeighbourhood(cn);
- //octree->getPointsInSphericalNeighbourhood(*cloud->getPoint(randIndex),radius,neighbours,level);
- size_t neihgboursCount = cn.neighbours.size();
- extractedPoints += static_cast<unsigned long long>(neihgboursCount);
- for (size_t k = 0; k < neihgboursCount; ++k)
- {
- cloud->setPointScalarValue(cn.neighbours[k].pointIndex, static_cast<ScalarType>(sqrt(cn.neighbours[k].squareDistd)));
- }
- }
- ccConsole::Print(tr("[CNE_TEST] Mean extraction time = %1 ms (radius = %2, height = %3, mean (neighbours) = %4))").arg(subTimer.elapsed()).arg(radius).arg(height).arg(static_cast<double>(extractedPoints) / samples, 0, 'f', 1));
- }
- else
- {
- ccConsole::Error(tr("Failed to compute octree!"));
- }
- ccScalarField* sf = static_cast<ccScalarField*>(cloud->getScalarField(sfIdx));
- sf->computeMinAndMax();
- sf->showNaNValuesInGrey(false);
- cloud->setCurrentDisplayedScalarField(sfIdx);
- cloud->showSF(true);
- addToDB(cloud);
- refreshAll();
- updateUI();
- }
- void MainWindow::doActionCreateCloudFromEntCenters()
- {
- size_t selNum = getSelectedEntities().size();
- ccPointCloud* centers = new ccPointCloud(tr("centers"));
- if (!centers->reserve(static_cast<unsigned>(selNum)))
- {
- ccLog::Error(tr("Not enough memory!"));
- delete centers;
- centers = nullptr;
- return;
- }
- //look for clouds
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
-
- if (cloud == nullptr)
- {
- continue;
- }
-
- centers->addPoint(cloud->getOwnBB().getCenter());
-
- //we display the cloud in the same window as the first (selected) cloud we encounter
- if (!centers->getDisplay())
- {
- centers->setDisplay(cloud->getDisplay());
- }
- }
- }
- if (centers->size() == 0)
- {
- ccLog::Error(tr("No cloud in selection?!"));
- delete centers;
- centers = nullptr;
- }
- else
- {
- centers->resize(centers->size());
- centers->setPointSize(10);
- centers->setVisible(true);
- addToDB(centers);
- }
- }
- void MainWindow::doActionComputeBestICPRmsMatrix()
- {
- //look for clouds
- std::vector<ccPointCloud*> clouds;
- try
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- if (cloud)
- {
- clouds.push_back(cloud);
- }
- }
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Error(tr("Not enough memory!"));
- return;
- }
- size_t cloudCount = clouds.size();
- if (cloudCount < 2)
- {
- ccLog::Error(tr("Need at least two clouds!"));
- return;
- }
- //init matrices
- std::vector<double> rmsMatrix;
- std::vector<ccGLMatrix> matrices;
- std::vector< std::pair<double, double> > matrixAngles;
- try
- {
- rmsMatrix.resize(cloudCount*cloudCount, 0);
- //init all possible transformations
- static const double angularStep_deg = 45.0;
- unsigned phiSteps = static_cast<unsigned>(360.0 / angularStep_deg);
- assert(CCCoreLib::LessThanEpsilon(std::abs(360.0 - phiSteps * angularStep_deg)));
- unsigned thetaSteps = static_cast<unsigned>(180.0 / angularStep_deg);
- assert(CCCoreLib::LessThanEpsilon(std::abs(180.0 - thetaSteps * angularStep_deg)));
- unsigned rotCount = phiSteps * (thetaSteps - 1) + 2;
- matrices.reserve(rotCount);
- matrixAngles.reserve(rotCount);
- for (unsigned j = 0; j <= thetaSteps; ++j)
- {
- //we want to cover the full [0-180] interval! ([-90;90] in fact)
- double theta_deg = j * angularStep_deg - 90.0;
- for (unsigned i = 0; i < phiSteps; ++i)
- {
- double phi_deg = i * angularStep_deg;
- ccGLMatrix trans;
- trans.initFromParameters( static_cast<float>( CCCoreLib::DegreesToRadians( phi_deg ) ),
- static_cast<float>( CCCoreLib::DegreesToRadians( theta_deg ) ),
- 0,
- CCVector3(0,0,0) );
- matrices.push_back(trans);
- matrixAngles.push_back( std::pair<double,double>(phi_deg,theta_deg) );
- //for poles, no need to rotate!
- if (j == 0 || j == thetaSteps)
- break;
- }
- }
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Error(tr("Not enough memory!"));
- return;
- }
- //let's start!
- {
- ccProgressDialog pDlg(true, this);
- pDlg.setMethodTitle(tr("Testing all possible positions"));
- pDlg.setInfo(tr("%1 clouds and %2 positions").arg(cloudCount).arg(matrices.size()));
- CCCoreLib::NormalizedProgress nProgress(&pDlg, static_cast<unsigned>(((cloudCount*(cloudCount - 1)) / 2)*matrices.size()));
- pDlg.start();
- QApplication::processEvents();
- //#define TEST_GENERATION
- #ifdef TEST_GENERATION
- ccPointCloud* testSphere = new ccPointCloud();
- testSphere->reserve(matrices.size());
- #endif
- for (size_t i = 0; i < cloudCount - 1; ++i)
- {
- ccPointCloud* A = clouds[i];
- A->computeOctree();
- for (size_t j = i + 1; j < cloudCount; ++j)
- {
- ccGLMatrix transBToZero;
- transBToZero.toIdentity();
- transBToZero.setTranslation(-clouds[j]->getOwnBB().getCenter());
- ccGLMatrix transFromZeroToA;
- transFromZeroToA.toIdentity();
- transFromZeroToA.setTranslation(A->getOwnBB().getCenter());
- #ifndef TEST_GENERATION
- double minRMS = -1.0;
- int bestMatrixIndex = -1;
- ccPointCloud* bestB = nullptr;
- #endif
- for (size_t k = 0; k < matrices.size(); ++k)
- {
- ccPointCloud* B = clouds[j]->cloneThis();
- if (!B)
- {
- ccLog::Error(tr("Not enough memory!"));
- return;
- }
- ccGLMatrix BtoA = transFromZeroToA * matrices[k] * transBToZero;
- B->applyRigidTransformation(BtoA);
- #ifndef TEST_GENERATION
- double finalRMS = 0.0;
- unsigned finalPointCount = 0;
- CCCoreLib::ICPRegistrationTools::RESULT_TYPE result;
- CCCoreLib::ICPRegistrationTools::ScaledTransformation registerTrans;
- CCCoreLib::ICPRegistrationTools::Parameters params;
- {
- params.convType = CCCoreLib::ICPRegistrationTools::MAX_ERROR_CONVERGENCE;
- params.minRMSDecrease = 1.0e-6;
- }
- result = CCCoreLib::ICPRegistrationTools::Register(A, nullptr, B, params, registerTrans, finalRMS, finalPointCount);
- if (result >= CCCoreLib::ICPRegistrationTools::ICP_ERROR)
- {
- delete B;
- if (bestB)
- delete bestB;
- ccLog::Error(tr("An error occurred while performing ICP!"));
- return;
- }
- if (minRMS < 0 || finalRMS < minRMS)
- {
- minRMS = finalRMS;
- bestMatrixIndex = static_cast<int>(k);
- std::swap(bestB, B);
- }
- if (B)
- {
- delete B;
- B = nullptr;
- }
- #else
- addToDB(B);
- //Test sphere
- CCVector3 Y(0,1,0);
- matrices[k].apply(Y);
- testSphere->addPoint(Y);
- #endif
- if (!nProgress.oneStep())
- {
- //process cancelled by user
- return;
- }
- }
- #ifndef TEST_GENERATION
- if (bestMatrixIndex >= 0)
- {
- assert(bestB);
- ccHObject* group = new ccHObject(tr("Best case #%1 / #%2 - RMS = %3").arg(i+1).arg(j+1).arg(minRMS));
- group->addChild(bestB);
- group->setDisplay_recursive(A->getDisplay());
- addToDB(group);
- ccLog::Print(tr("[DoActionComputeBestICPRmsMatrix] Comparison #%1 / #%2: min RMS = %3 (phi = %4 / theta = %5 deg.)").arg(i+1).arg(j+1).arg(minRMS).arg(matrixAngles[bestMatrixIndex].first).arg(matrixAngles[bestMatrixIndex].second));
- }
- else
- {
- assert(!bestB);
- ccLog::Warning(tr("[DoActionComputeBestICPRmsMatrix] Comparison #%1 / #%2: INVALID").arg(i+1).arg(j+1));
- }
- rmsMatrix[i*cloudCount + j] = minRMS;
- #else
- addToDB(testSphere);
- i = cloudCount;
- break;
- #endif
- }
- }
- }
- //export result as a CSV file
- #ifdef TEST_GENERATION
- if (false)
- #endif
- {
- //persistent settings
- QSettings settings;
- settings.beginGroup(ccPS::SaveFile());
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- QString outputFilename = QFileDialog::getSaveFileName( this,
- tr("Select output file"),
- currentPath,
- "*.csv",
- nullptr,
- CCFileDialogOptions());
- if (outputFilename.isEmpty())
- return;
- QFile fp(outputFilename);
- if (fp.open(QFile::Text | QFile::WriteOnly))
- {
- QTextStream stream(&fp);
- //header
- {
- stream << "RMS";
- for ( ccPointCloud *cloud : clouds )
- {
- stream << ';';
- stream << cloud->getName();
- }
- stream << endl;
- }
- //rows
- for (size_t j = 0; j < cloudCount; ++j)
- {
- stream << clouds[j]->getName();
- stream << ';';
- for (size_t i = 0; i < cloudCount; ++i)
- {
- stream << rmsMatrix[j*cloudCount+i];
- stream << ';';
- }
- stream << endl;
- }
- ccLog::Print(tr("[DoActionComputeBestICPRmsMatrix] Job done"));
- }
- else
- {
- ccLog::Error(tr("Failed to save output file?!"));
- }
- }
- }
- void MainWindow::doActionExportPlaneInfo()
- {
- ccHObject::Container planes;
- const ccHObject::Container& selectedEntities = getSelectedEntities();
- if (selectedEntities.size() == 1 && selectedEntities.front()->isA(CC_TYPES::HIERARCHY_OBJECT))
- {
- //a group
- selectedEntities.front()->filterChildren(planes, true, CC_TYPES::PLANE, false);
- }
- else
- {
- for (ccHObject* ent : selectedEntities)
- {
- if (ent->isKindOf(CC_TYPES::PLANE))
- {
- //a single plane
- planes.push_back(static_cast<ccPlane*>(ent));
- }
- }
- }
- if (planes.size() == 0)
- {
- ccLog::Error(tr("No plane in selection"));
- return;
- }
- //persistent settings
- QSettings settings;
- settings.beginGroup(ccPS::SaveFile());
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- QString outputFilename = QFileDialog::getSaveFileName( this,
- tr("Select output file"),
- currentPath,
- "*.csv",
- nullptr,
- CCFileDialogOptions());
- if (outputFilename.isEmpty())
- {
- //process cancelled by the user
- return;
- }
- QFile csvFile(outputFilename);
- if (!csvFile.open(QFile::WriteOnly | QFile::Text))
- {
- ccConsole::Error(tr("Failed to open file for writing! (check file permissions)"));
- return;
- }
- //save last saving location
- settings.setValue(ccPS::CurrentPath(), QFileInfo(outputFilename).absolutePath());
- settings.endGroup();
- //write CSV header
- QTextStream csvStream(&csvFile);
- csvStream << "Name;";
- csvStream << "Width;";
- csvStream << "Height;";
- csvStream << "Cx;";
- csvStream << "Cy;";
- csvStream << "Cz;";
- csvStream << "Nx;";
- csvStream << "Ny;";
- csvStream << "Nz;";
- csvStream << "Dip;";
- csvStream << "Dip dir;";
- csvStream << endl;
- QChar separator(';');
- //write one line per plane
- for (ccHObject* ent : planes)
- {
- ccPlane* plane = static_cast<ccPlane*>(ent);
-
- CCVector3 C = plane->getOwnBB().getCenter();
- CCVector3 N = plane->getNormal();
- PointCoordinateType dip_deg = 0;
- PointCoordinateType dipDir_deg = 0;
- ccNormalVectors::ConvertNormalToDipAndDipDir(N, dip_deg, dipDir_deg);
- csvStream << plane->getName() << separator; //Name
- csvStream << plane->getXWidth() << separator; //Width
- csvStream << plane->getYWidth() << separator; //Height
- csvStream << C.x << separator; //Cx
- csvStream << C.y << separator; //Cy
- csvStream << C.z << separator; //Cz
- csvStream << N.x << separator; //Nx
- csvStream << N.y << separator; //Ny
- csvStream << N.z << separator; //Nz
- csvStream << dip_deg << separator; //Dip
- csvStream << dipDir_deg << separator; //Dip direction
- csvStream << endl;
- }
- ccConsole::Print(tr("[I/O] File '%1' successfully saved (%2 plane(s))").arg(outputFilename).arg(planes.size()));
- csvFile.close();
- }
- void MainWindow::doActionExportCloudInfo()
- {
- //look for clouds
- ccHObject::Container clouds;
- const ccHObject::Container& selectedEntities = getSelectedEntities();
- if (selectedEntities.size() == 1 && selectedEntities.front()->isA(CC_TYPES::HIERARCHY_OBJECT))
- {
- //a group
- selectedEntities.front()->filterChildren(clouds, true, CC_TYPES::POINT_CLOUD, true);
- }
- else
- {
- for (ccHObject* entity : selectedEntities)
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- if (cloud)
- {
- clouds.push_back(cloud);
- }
- }
- }
- if (clouds.empty())
- {
- ccConsole::Error(tr("Select at least one point cloud!"));
- return;
- }
- //persistent settings
- QSettings settings;
- settings.beginGroup(ccPS::SaveFile());
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- QString outputFilename = QFileDialog::getSaveFileName( this,
- tr("Select output file"),
- currentPath,
- "*.csv",
- nullptr,
- CCFileDialogOptions());
- if (outputFilename.isEmpty())
- {
- //process cancelled by the user
- return;
- }
- QFile csvFile(outputFilename);
- if (!csvFile.open(QFile::WriteOnly | QFile::Text))
- {
- ccConsole::Error(tr("Failed to open file for writing! (check file permissions)"));
- return;
- }
- //save last saving location
- settings.setValue(ccPS::CurrentPath(), QFileInfo(outputFilename).absolutePath());
- settings.endGroup();
- //determine the maximum number of SFs
- unsigned maxSFCount = 0;
- for (ccHObject* entity : clouds)
- {
- maxSFCount = std::max<unsigned>(maxSFCount, static_cast<ccPointCloud*>(entity)->getNumberOfScalarFields());
- }
- //write CSV header
- QTextStream csvStream(&csvFile);
- csvStream << "Name;";
- csvStream << "Points;";
- csvStream << "meanX;";
- csvStream << "meanY;";
- csvStream << "meanZ;";
- {
- for (unsigned i = 0; i < maxSFCount; ++i)
- {
- QString sfIndex = QString("SF#%1").arg(i + 1);
- csvStream << sfIndex << " name;";
- csvStream << sfIndex << " valid values;";
- csvStream << sfIndex << " mean;";
- csvStream << sfIndex << " std.dev.;";
- csvStream << sfIndex << " sum;";
- }
- }
- csvStream << endl;
- //write one line per cloud
- {
- for (ccHObject* entity : clouds)
- {
- ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
- CCVector3 G = *CCCoreLib::Neighbourhood(cloud).getGravityCenter();
- csvStream << cloud->getName() << ';' /*"Name;"*/;
- csvStream << cloud->size() << ';' /*"Points;"*/;
- csvStream << G.x << ';' /*"meanX;"*/;
- csvStream << G.y << ';' /*"meanY;"*/;
- csvStream << G.z << ';' /*"meanZ;"*/;
- for (unsigned j = 0; j < cloud->getNumberOfScalarFields(); ++j)
- {
- CCCoreLib::ScalarField* sf = cloud->getScalarField(j);
- csvStream << QString::fromStdString(sf->getName()) << ';' /*"SF name;"*/;
- unsigned validCount = 0;
- double sfSum = 0.0;
- double sfSum2 = 0.0;
- for (unsigned k = 0; k < sf->currentSize(); ++k)
- {
- const ScalarType& val = sf->getValue(k);
- if (CCCoreLib::ScalarField::ValidValue(val))
- {
- ++validCount;
- sfSum += val;
- sfSum2 += static_cast<double>(val)*val;
- }
- }
- csvStream << validCount << ';' /*"SF valid values;"*/;
- if (validCount)
- {
- double mean = sfSum / validCount;
- csvStream << mean << ';' /*"SF mean;"*/;
- csvStream << sqrt(std::abs(sfSum2 / validCount - mean * mean)) << ';' /*"SF std.dev.;"*/;
- }
- else
- {
- csvStream << "N/A;" /*"SF mean;"*/;
- csvStream << "N/A;" /*"SF std.dev.;"*/;
- }
- csvStream << sfSum << ';' /*"SF sum;"*/;
- }
- csvStream << endl;
- }
- }
- ccConsole::Print(tr("[I/O] File '%1' successfully saved (%2 cloud(s))").arg(outputFilename).arg(clouds.size()));
- csvFile.close();
- }
- void MainWindow::doActionCloudCloudDist()
- {
- if (getSelectedEntities().size() != 2)
- {
- ccConsole::Error(tr("Select 2 point clouds!"));
- return;
- }
- if (!m_selectedEntities.front()->isKindOf(CC_TYPES::POINT_CLOUD) ||
- !m_selectedEntities.back()->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccConsole::Error(tr("Select 2 point clouds!"));
- return;
- }
- ccOrderChoiceDlg dlg( m_selectedEntities.front(), tr("Compared"),
- m_selectedEntities.back(), tr("Reference"),
- this );
- if (!dlg.exec())
- return;
- ccGenericPointCloud* compCloud = ccHObjectCaster::ToGenericPointCloud(dlg.getFirstEntity());
- ccGenericPointCloud* refCloud = ccHObjectCaster::ToGenericPointCloud(dlg.getSecondEntity());
- //assert(!m_compDlg);
- if (m_compDlg)
- delete m_compDlg;
- m_compDlg = new ccComparisonDlg(compCloud, refCloud, ccComparisonDlg::CLOUDCLOUD_DIST, this);
- if (!m_compDlg->initDialog())
- {
- ccConsole::Error(tr("Failed to initialize comparison dialog"));
- delete m_compDlg;
- m_compDlg = nullptr;
- return;
- }
- connect(m_compDlg, &QDialog::finished, this, &MainWindow::deactivateComparisonMode);
- m_compDlg->show();
- //cDlg.setModal(false);
- //cDlg.exec();
- freezeUI(true);
- }
- void MainWindow::doActionCloudMeshDist()
- {
- if (getSelectedEntities().size() != 2)
- {
- ccConsole::Error(tr("Select 2 entities!"));
- return;
- }
- bool isMesh[2]{ false,false };
- unsigned meshNum = 0;
- unsigned cloudNum = 0;
- for (unsigned i = 0; i < 2; ++i)
- {
- if (m_selectedEntities[i]->isKindOf(CC_TYPES::MESH))
- {
- ++meshNum;
- isMesh[i] = true;
- }
- else if (m_selectedEntities[i]->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ++cloudNum;
- }
- }
- if (meshNum == 0)
- {
- ccConsole::Error(tr("Select at least one mesh!"));
- return;
- }
- else if (meshNum + cloudNum < 2)
- {
- ccConsole::Error(tr("Select one mesh and one cloud or two meshes!"));
- return;
- }
- ccHObject* compEnt = nullptr;
- ccGenericMesh* refMesh = nullptr;
- if (meshNum == 1)
- {
- compEnt = m_selectedEntities[isMesh[0] ? 1 : 0];
- refMesh = ccHObjectCaster::ToGenericMesh(m_selectedEntities[isMesh[0] ? 0 : 1]);
- if (refMesh->isKindOf(CC_TYPES::PRIMITIVE))
- {
- static bool DontShowPrimitiveDistWarning = false;
- if (!DontShowPrimitiveDistWarning)
- {
- QMessageBox::StandardButton answer = QMessageBox::warning(
- this,
- tr("Distance to primitive"),
- tr("Computing distances to a primitive is faster and more accurate with the 'Tools > Distances > Cloud / Primitive Dist.' tool.\nDo you want to use this other tool instead?"),
- QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll,
- QMessageBox::Yes
- );
- if (answer == QMessageBox::Yes)
- {
- return doActionCloudPrimitiveDist();
- }
- else if (answer == QMessageBox::NoToAll)
- {
- DontShowPrimitiveDistWarning = true;
- }
- }
- }
- }
- else
- {
- ccOrderChoiceDlg dlg( m_selectedEntities.front(), tr("Compared"),
- m_selectedEntities.back(), tr("Reference"),
- this );
- if (!dlg.exec())
- return;
- compEnt = dlg.getFirstEntity();
- refMesh = ccHObjectCaster::ToGenericMesh(dlg.getSecondEntity());
- }
- //assert(!m_compDlg);
- if (m_compDlg)
- delete m_compDlg;
- m_compDlg = new ccComparisonDlg(compEnt, refMesh, ccComparisonDlg::CLOUDMESH_DIST, this);
- if (!m_compDlg->initDialog())
- {
- ccConsole::Error(tr("Failed to initialize comparison dialog"));
- delete m_compDlg;
- m_compDlg = nullptr;
- return;
- }
- connect(m_compDlg, &QDialog::finished, this, &MainWindow::deactivateComparisonMode);
- m_compDlg->show();
- freezeUI(true);
- }
- void MainWindow::doActionCloudPrimitiveDist()
- {
- ccHObject::Container clouds;
- ccHObject* refEntity = nullptr;
- for (ccHObject* entity : getSelectedEntities())
- {
- if (entity->isKindOf(CC_TYPES::PRIMITIVE) || entity->isA(CC_TYPES::POLY_LINE))
- {
- if (entity->isA(CC_TYPES::PLANE) ||
- entity->isA(CC_TYPES::SPHERE) ||
- entity->isA(CC_TYPES::CYLINDER) ||
- entity->isA(CC_TYPES::CONE) ||
- entity->isA(CC_TYPES::BOX) ||
- entity->isA(CC_TYPES::POLY_LINE))
- {
- if (!refEntity)
- {
- // first primitive encountered
- refEntity = entity;
- }
- else
- {
- ccConsole::Error(tr("Select only one primitive (Plane/Box/Sphere/Cylinder/Cone) or polyline"));
- return;
- }
- }
- }
- else if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- clouds.push_back(entity);
- }
- }
- if (!refEntity)
- {
- ccConsole::Error(tr("Select one prmitive (Plane/Box/Sphere/Cylinder/Cone) or a polyline"));
- return;
- }
- if (clouds.empty())
- {
- ccConsole::Error(tr("Select at least one cloud"));
- return;
- }
-
- ccPrimitiveDistanceDlg pDD{ this };
- static bool s_treatPlanesAsBounded = false;
- static bool s_signedDist = true;
- static bool s_flipNormals = false;
- if (refEntity->isA(CC_TYPES::PLANE))
- {
- pDD.treatPlanesAsBoundedCheckBox->setEnabled(true);
- pDD.treatPlanesAsBoundedCheckBox->setChecked(s_treatPlanesAsBounded);
- }
- else
- {
- pDD.treatPlanesAsBoundedCheckBox->setEnabled(false);
- }
- pDD.flipNormalsCheckBox->setChecked(s_flipNormals);
- pDD.signedDistCheckBox->setChecked(s_signedDist);
- if (!refEntity->isA(CC_TYPES::POLY_LINE))
- {
- if (!pDD.exec())
- {
- return;
- }
- }
- s_signedDist = pDD.signedDistances();
- s_flipNormals = pDD.flipNormals();
- s_treatPlanesAsBounded = pDD.treatPlanesAsBounded();
- size_t errorCount = 0;
- for (auto &cloud : clouds)
- {
- ccPointCloud* compEnt = ccHObjectCaster::ToPointCloud(cloud);
- int sfIdx = compEnt->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
- if (sfIdx < 0)
- {
- //we need to create a new scalar field
- sfIdx = compEnt->addScalarField(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
- if (sfIdx < 0)
- {
- ccLog::Warning(tr("[Compute Primitive Distances] [Cloud: %1] Couldn't allocate a new scalar field for computing distances! Try to free some memory ...").arg(compEnt->getName()));
- ++errorCount;
- continue;
- }
- }
- compEnt->setCurrentScalarField(sfIdx);
- if (!compEnt->enableScalarField())
- {
- ccLog::Warning(tr("[Compute Primitive Distances] [Cloud: %1] Not enough memory").arg(compEnt->getName()));
- ++errorCount;
- continue;
- }
- compEnt->forEach(CCCoreLib::ScalarFieldTools::SetScalarValueToNaN);
- int returnCode = 0;
- QString errString = tr("[Compute Primitive Distances] Cloud to %1 distance computation failed (error code = %2)");
- switch (refEntity->getClassID())
- {
- case CC_TYPES::SPHERE:
- {
- if (!(returnCode = CCCoreLib::DistanceComputationTools::computeCloud2SphereEquation(compEnt, refEntity->getOwnBB().getCenter(), static_cast<ccSphere*>(refEntity)->getRadius(), s_signedDist)))
- ccConsole::Error(errString.arg(tr("Sphere")).arg(returnCode));
- break;
- }
- case CC_TYPES::PLANE:
- {
- ccPlane* plane = static_cast<ccPlane*>(refEntity);
- if (s_treatPlanesAsBounded)
- {
- CCCoreLib::SquareMatrix rotationTransform(plane->getTransformation().data(), true);
- if (!(returnCode = CCCoreLib::DistanceComputationTools::computeCloud2RectangleEquation(compEnt, plane->getXWidth(), plane->getYWidth(), rotationTransform, plane->getCenter(), s_signedDist)))
- {
- ccConsole::Warning(errString.arg(tr("Bounded Plane")).arg(returnCode));
- ++errorCount;
- }
- }
- else
- {
- if (!(returnCode = CCCoreLib::DistanceComputationTools::computeCloud2PlaneEquation(compEnt, static_cast<ccPlane*>(refEntity)->getEquation(), s_signedDist)))
- {
- ccConsole::Warning(errString.arg(tr("Infinite Plane")).arg(returnCode));
- ++errorCount;
- }
- }
- break;
- }
- case CC_TYPES::CYLINDER:
- {
- if (!(returnCode = CCCoreLib::DistanceComputationTools::computeCloud2CylinderEquation(compEnt, static_cast<ccCylinder*>(refEntity)->getBottomCenter(), static_cast<ccCylinder*>(refEntity)->getTopCenter(), static_cast<ccCylinder*>(refEntity)->getBottomRadius(), s_signedDist)))
- {
- ccConsole::Warning(errString.arg(tr("Cylinder")).arg(returnCode));
- ++errorCount;
- }
- break;
- }
- case CC_TYPES::CONE:
- {
- if (!(returnCode = CCCoreLib::DistanceComputationTools::computeCloud2ConeEquation(compEnt, static_cast<ccCone*>(refEntity)->getLargeCenter(), static_cast<ccCone*>(refEntity)->getSmallCenter(), static_cast<ccCone*>(refEntity)->getLargeRadius(), static_cast<ccCone*>(refEntity)->getSmallRadius(), s_signedDist)))
- {
- ccConsole::Warning(errString.arg(tr("Cone")).arg(returnCode));
- ++errorCount;
- }
- break;
- }
- case CC_TYPES::BOX:
- {
- const ccGLMatrix& glTransform = refEntity->getGLTransformationHistory();
- CCCoreLib::SquareMatrix rotationTransform(glTransform.data(), true);
- CCVector3 boxCenter = glTransform.getColumnAsVec3D(3);
- if (!(returnCode = CCCoreLib::DistanceComputationTools::computeCloud2BoxEquation(compEnt, static_cast<ccBox*>(refEntity)->getDimensions(), rotationTransform, boxCenter, s_signedDist)))
- {
- ccConsole::Warning(errString.arg(tr("Box")).arg(returnCode));
- ++errorCount;
- }
- break;
- }
- case CC_TYPES::POLY_LINE:
- {
- ccPolyline* line = static_cast<ccPolyline*>(refEntity);
- returnCode = CCCoreLib::DistanceComputationTools::computeCloud2PolylineEquation(compEnt, line);
- if (!returnCode)
- {
- ccConsole::Warning(errString.arg(tr("Polyline")).arg(returnCode));
- ++errorCount;
- }
- break;
- }
- default:
- {
- ccConsole::Error(tr("Unsupported primitive type")); //Shouldn't ever reach here...
- return;
- }
- }
- QString sfName(CC_CLOUD2PRIMITIVE_DISTANCES_DEFAULT_SF_NAME);
- if (!refEntity->isKindOf(CC_TYPES::POLY_LINE))
- {
- if (s_signedDist)
- {
- sfName = CC_CLOUD2PRIMITIVE_SIGNED_DISTANCES_DEFAULT_SF_NAME;
- }
- if (s_flipNormals)
- {
- compEnt->forEach(CCCoreLib::ScalarFieldTools::SetScalarValueInverted);
- sfName += "[-]";
- }
- }
- int _sfIdx = compEnt->getScalarFieldIndexByName(sfName.toStdString());
- if (_sfIdx >= 0)
- {
- compEnt->deleteScalarField(_sfIdx);
- //we update sfIdx because indexes are all messed up after deletion
- sfIdx = compEnt->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
- }
- compEnt->renameScalarField(sfIdx, sfName.toStdString());
- ccScalarField* sf = static_cast<ccScalarField*>(compEnt->getScalarField(sfIdx));
- if (sf)
- {
- ScalarType mean;
- ScalarType variance;
- sf->computeMinAndMax();
- sf->computeMeanAndVariance(mean, &variance);
- ccLog::Print(tr("[Compute Primitive Distances] [Primitive: %1] [Cloud: %2] [%3] Mean distance = %4 / std deviation = %5")
- .arg(refEntity->getName())
- .arg(compEnt->getName())
- .arg(sfName)
- .arg(mean)
- .arg(sqrt(variance)));
- }
- compEnt->setCurrentDisplayedScalarField(sfIdx);
- compEnt->showSF(sfIdx >= 0);
- compEnt->prepareDisplayForRefresh_recursive();
- }
- if (errorCount != 0)
- {
- ccLog::Error(tr("%1 error(s) occurred: refer to the Console (F8)").arg(errorCount));
- }
- MainWindow::UpdateUI();
- MainWindow::RefreshAllGLWindow(false);
- }
- void MainWindow::deactivateComparisonMode(int result)
- {
- //DGM: a bug apperead with recent changes (from CC or QT?)
- //which prevent us from deleting the dialog right away...
- //(it seems that QT has not yet finished the dialog closing
- //when the 'finished' signal is sent).
- //if(m_compDlg)
- // delete m_compDlg;
- //m_compDlg = 0;
- //if the comparison is a success, we select only the compared entity
- if (m_compDlg && result == QDialog::Accepted && m_ccRoot)
- {
- ccHObject* compEntity = m_compDlg->getComparedEntity();
- if (compEntity)
- {
- m_ccRoot->selectEntity(compEntity);
- }
- }
- freezeUI(false);
- updateUI();
- }
- void MainWindow::toggleActiveWindowSunLight()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->toggleSunLight();
- win->redraw(false);
- }
- }
- void MainWindow::toggleActiveWindowCustomLight()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->toggleCustomLight();
- win->redraw(false);
- }
- }
- void MainWindow::toggleActiveWindowAutoPickRotCenter(bool state)
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->setAutoPickPivotAtCenter(state);
- //save the option
- {
- QSettings settings;
- settings.setValue(ccPS::AutoPickRotationCenter(), state);
- }
- }
- }
- void MainWindow::toggleActiveWindowShowCursorCoords(bool state)
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->showCursorCoordinates(state);
- }
- }
- void MainWindow::toggleActiveWindowStereoVision(bool state)
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- bool isActive = win->stereoModeIsEnabled();
- if (isActive == state)
- {
- //nothing to do
- return;
- }
- if (isActive)
- {
- win->disableStereoMode();
- if ( win->getStereoParams().glassType == ccGLWindowInterface::StereoParams::NVIDIA_VISION
- || win->getStereoParams().glassType == ccGLWindowInterface::StereoParams::GENERIC_STEREO_DISPLAY)
- {
- //disable (exclusive) full screen
- m_UI->actionExclusiveFullScreen->setChecked(false);
- }
- }
- else
- {
- //display a parameters dialog
- ccStereoModeDlg smDlg(this);
- smDlg.setParameters(win->getStereoParams());
- if (!smDlg.exec())
- {
- //cancelled by the user
- m_UI->actionEnableStereo->blockSignals(true);
- m_UI->actionEnableStereo->setChecked(false);
- m_UI->actionEnableStereo->blockSignals(false);
- return;
- }
- ccGLWindowInterface::StereoParams params = smDlg.getParameters();
- ccLog::WarningDebug("Stereo strength: " + QString::number(params.stereoStrength));
- if (!ccGLWindowInterface::StereoSupported() && !params.isAnaglyph())
- {
- ccLog::Error(tr("It seems your graphic card doesn't support Quad Buffered Stereo rendering"));
- //activation of the stereo mode failed: cancel selection
- m_UI->actionEnableStereo->blockSignals(true);
- m_UI->actionEnableStereo->setChecked(false);
- m_UI->actionEnableStereo->blockSignals(false);
- return;
- }
- //force perspective state!
- if (!win->getViewportParameters().perspectiveView)
- {
- setCenteredPerspectiveView(win, false);
- }
- if ( params.glassType == ccGLWindowInterface::StereoParams::NVIDIA_VISION
- || params.glassType == ccGLWindowInterface::StereoParams::GENERIC_STEREO_DISPLAY)
- {
- //force (exclusive) full screen
- m_UI->actionExclusiveFullScreen->setChecked(true);
- }
- if (smDlg.updateFOV())
- {
- //set the right FOV
- double fov_deg = 2 * CCCoreLib::RadiansToDegrees( std::atan(params.screenWidth_mm / (2.0 * params.screenDistance_mm)) );
- ccLog::Print(tr("[Stereo] F.O.V. forced to %1 deg.").arg(fov_deg));
- win->setFov(fov_deg);
- }
- if (!win->enableStereoMode(params))
- {
- if ( params.glassType == ccGLWindowInterface::StereoParams::NVIDIA_VISION
- || params.glassType == ccGLWindowInterface::StereoParams::GENERIC_STEREO_DISPLAY)
- {
- //disable (exclusive) full screen
- m_UI->actionExclusiveFullScreen->setChecked(false);
- }
- //activation of the stereo mode failed: cancel selection
- m_UI->actionEnableStereo->blockSignals(true);
- m_UI->actionEnableStereo->setChecked(false);
- m_UI->actionEnableStereo->blockSignals(false);
- }
- }
- win->redraw();
- }
- }
- bool MainWindow::checkStereoMode(ccGLWindowInterface* win)
- {
- assert(win);
- if (win && win->getViewportParameters().perspectiveView && win->stereoModeIsEnabled())
- {
- ccGLWindowInterface::StereoParams params = win->getStereoParams();
- bool wasExclusiveFullScreen = win->exclusiveFullScreen();
- if (wasExclusiveFullScreen)
- {
- win->toggleExclusiveFullScreen(false);
- }
- win->disableStereoMode();
- if (QMessageBox::question( this,
- tr("Stereo mode"),
- tr("Stereo-mode only works in perspective mode. Do you want to disable it?"),
- QMessageBox::Yes,
- QMessageBox::No) == QMessageBox::No )
- {
- if (wasExclusiveFullScreen)
- {
- win->toggleExclusiveFullScreen(true);
- win->enableStereoMode(params);
- }
- return false;
- }
- else
- {
- if (win == getActiveGLWindow())
- {
- m_UI->actionEnableStereo->setChecked(false);
- }
- else
- {
- assert(false);
- m_UI->actionEnableStereo->blockSignals(true);
- m_UI->actionEnableStereo->setChecked(false);
- m_UI->actionEnableStereo->blockSignals(false);
- }
- }
- }
- return true;
- }
- void MainWindow::toggleActiveWindowCenteredPerspective()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- const ccViewportParameters& params = win->getViewportParameters();
- if (params.perspectiveView && params.objectCenteredView && !checkStereoMode(win)) //we need to check this only if we are already in object-centered perspective mode
- {
- return;
- }
- win->togglePerspective(true);
- win->redraw(false);
- updateViewModePopUpMenu(win);
- updatePivotVisibilityPopUpMenu(win);
- }
- }
- void MainWindow::toggleActiveWindowViewerBasedPerspective()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- const ccViewportParameters& params = win->getViewportParameters();
- if (params.perspectiveView && !params.objectCenteredView && !checkStereoMode(win)) //we need to check this only if we are already in viewer-based perspective mode
- {
- return;
- }
- win->togglePerspective(false);
- win->redraw(false);
- updateViewModePopUpMenu(win);
- updatePivotVisibilityPopUpMenu(win);
- }
- }
- void MainWindow::createSinglePointCloud()
- {
- // ask the user to input the point coordinates
- static CCVector3d s_lastPoint(0, 0, 0);
- static size_t s_lastPointIndex = 0;
- ccAskThreeDoubleValuesDlg axisDlg("x", "y", "z", -1.0e12, 1.0e12, s_lastPoint.x, s_lastPoint.y, s_lastPoint.z, 4, tr("Point coordinates"), this);
- if (axisDlg.buttonBox->button(QDialogButtonBox::Ok))
- axisDlg.buttonBox->button(QDialogButtonBox::Ok)->setFocus();
- if (!axisDlg.exec())
- return;
- s_lastPoint.x = axisDlg.doubleSpinBox1->value();
- s_lastPoint.y = axisDlg.doubleSpinBox2->value();
- s_lastPoint.z = axisDlg.doubleSpinBox3->value();
- // create the cloud
- ccPointCloud* cloud = new ccPointCloud();
- if (!cloud->reserve(1))
- {
- delete cloud;
- ccLog::Error(tr("Not enough memory!"));
- return;
- }
- cloud->setName(tr("Point #%1").arg(++s_lastPointIndex));
- cloud->addPoint(s_lastPoint.toPC());
- cloud->setPointSize(5);
- // add it to the DB tree
- addToDB(cloud, true, true, true, true);
- // select it
- m_ccRoot->unselectAllEntities();
- setSelectedInDB(cloud, true);
- }
- void MainWindow::createPointCloudFromClipboard()
- {
- const QClipboard* clipboard = QApplication::clipboard();
- assert(clipboard);
- const QMimeData* mimeData = clipboard->mimeData();
- if (!mimeData)
- {
- ccLog::Warning(tr("Clipboard is empty"));
- return;
- }
- if (!mimeData->hasText())
- {
- ccLog::Error("ASCII/text data expected");
- return;
- }
- // try to convert the data to a point cloud
- FileIOFilter::LoadParameters parameters;
- {
- parameters.alwaysDisplayLoadDialog = true;
- parameters.shiftHandlingMode = ccGlobalShiftManager::DIALOG_IF_NECESSARY;
- parameters.parentWidget = this;
- }
- ccHObject container;
- QByteArray data = mimeData->data("text/plain");
- CC_FILE_ERROR result = AsciiFilter().loadAsciiData(data, tr("Clipboard"), container, parameters);
- if (result != CC_FERR_NO_ERROR)
- {
- FileIOFilter::DisplayErrorMessage(result, tr("loading"), tr("from the clipboard"));
- return;
- }
- // we only expect clouds
- ccHObject::Container clouds;
- if (container.filterChildren(clouds, true, CC_TYPES::POINT_CLOUD) == 0)
- {
- assert(false);
- ccLog::Error(tr("No cloud loaded"));
- return;
- }
- // detach the clouds from the loading container
- for (ccHObject* cloud : clouds)
- {
- if (cloud)
- {
- container.removeDependencyWith(cloud);
- }
- }
- container.removeAllChildren();
- // retrieve or create the group to store the 'clipboard' clouds
- ccHObject* clipboardGroup = nullptr;
- {
- static unsigned s_clipboardGroupID = 0;
- if (s_clipboardGroupID != 0)
- {
- clipboardGroup = dbRootObject()->find(s_clipboardGroupID);
- if (nullptr == clipboardGroup)
- {
- // can't find the previous group
- s_clipboardGroupID = 0;
- }
- }
- if (s_clipboardGroupID == 0)
- {
- clipboardGroup = new ccHObject(tr("Clipboard"));
- s_clipboardGroupID = clipboardGroup->getUniqueID();
- addToDB(clipboardGroup, false, false, false, false);
- }
- }
- assert(clipboardGroup);
- bool normalsDisplayedByDefault = ccOptions::Instance().normalsDisplayedByDefault;
- for (ccHObject* cloud : clouds)
- {
- if (cloud)
- {
- clipboardGroup->addChild(cloud);
- cloud->setName(tr("Cloud #%1").arg(clipboardGroup->getChildrenNumber()));
- if (!normalsDisplayedByDefault)
- {
- // disable the normals on all loaded clouds!
- static_cast<ccGenericPointCloud*>(cloud)->showNormals(false);
- }
- }
- }
- // eventually, we can add the clouds to the DB tree
- for (size_t i = 0; i < clouds.size(); ++i)
- {
- ccHObject* cloud = clouds[i];
- if (cloud)
- {
- bool lastCloud = (i + 1 == clouds.size());
- addToDB(cloud, lastCloud, lastCloud, true, lastCloud);
- }
- }
- QMainWindow::statusBar()->showMessage(tr("%1 cloud(s) loaded from the clipboard").arg(clouds.size()), 2000);
- }
- void MainWindow::toggleLockRotationAxis()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- bool wasLocked = win->isRotationAxisLocked();
- bool isLocked = !wasLocked;
- static CCVector3d s_lastAxis(0.0, 0.0, 1.0);
- if (isLocked)
- {
- ccAskThreeDoubleValuesDlg axisDlg("x", "y", "z", -1.0e12, 1.0e12, s_lastAxis.x, s_lastAxis.y, s_lastAxis.z, 4, tr("Lock rotation axis"), this);
- if (axisDlg.buttonBox->button(QDialogButtonBox::Ok))
- axisDlg.buttonBox->button(QDialogButtonBox::Ok)->setFocus();
- if (!axisDlg.exec())
- return;
- s_lastAxis.x = axisDlg.doubleSpinBox1->value();
- s_lastAxis.y = axisDlg.doubleSpinBox2->value();
- s_lastAxis.z = axisDlg.doubleSpinBox3->value();
- }
- win->lockRotationAxis(isLocked, s_lastAxis);
- m_UI->actionLockRotationAxis->blockSignals(true);
- m_UI->actionLockRotationAxis->setChecked(isLocked);
- m_UI->actionLockRotationAxis->blockSignals(false);
- if (isLocked)
- {
- win->displayNewMessage(tr("[ROTATION LOCKED]"), ccGLWindowInterface::UPPER_CENTER_MESSAGE, false, 24 * 3600, ccGLWindowInterface::ROTAION_LOCK_MESSAGE);
- }
- else
- {
- win->displayNewMessage(QString(), ccGLWindowInterface::UPPER_CENTER_MESSAGE, false, 0, ccGLWindowInterface::ROTAION_LOCK_MESSAGE);
- }
- win->redraw(true, false);
- }
- }
- void MainWindow::doActionEnableBubbleViewMode()
- {
- //special case: the selected entity is a TLS sensor or a cloud with a TLS sensor
- if (m_ccRoot)
- {
- ccHObject::Container selectedEntities;
- m_ccRoot->getSelectedEntities(selectedEntities);
- if (selectedEntities.size() == 1)
- {
- ccHObject* ent = selectedEntities.front();
- ccGBLSensor* sensor = nullptr;
- if (ent->isA(CC_TYPES::GBL_SENSOR))
- {
- sensor = static_cast<ccGBLSensor*>(ent);
- }
- else if (ent->isA(CC_TYPES::POINT_CLOUD))
- {
- ccHObject::Container sensors;
- ent->filterChildren(sensors, false, CC_TYPES::GBL_SENSOR, true);
- if (sensors.size() >= 1)
- {
- sensor = static_cast<ccGBLSensor*>(sensors.front());
- }
- }
- if (sensor)
- {
- sensor->applyViewport();
- return;
- }
- }
- }
- //otherwise we simply enable the bubble view mode in the active 3D view
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->setBubbleViewMode(true);
- win->redraw(false);
- }
- }
- void MainWindow::doActionDeleteShader()
- {
- ccGLWindowInterface* win = getActiveGLWindow();
- if (win)
- {
- win->setShader(nullptr);
- }
- }
- void MainWindow::removeFromDB(ccHObject* obj, bool autoDelete/*=true*/)
- {
- if (!obj)
- return;
- //remove dependency to avoid deleting the object when removing it from DB tree
- if (!autoDelete && obj->getParent())
- obj->getParent()->removeDependencyWith(obj);
- if (m_ccRoot)
- m_ccRoot->removeElement(obj);
- }
- void MainWindow::setSelectedInDB(ccHObject* obj, bool selected)
- {
- if (obj && m_ccRoot)
- {
- if (selected)
- m_ccRoot->selectEntity(obj);
- else
- m_ccRoot->unselectEntity(obj);
- }
- }
- void MainWindow::addToDB( ccHObject* obj,
- bool updateZoom/*=true*/,
- bool autoExpandDBTree/*=true*/,
- bool checkDimensions/*=true*/,
- bool autoRedraw/*=true*/)
- {
- //let's check that the new entity is not too big nor too far from scene center!
- if (checkDimensions)
- {
- //get entity bounding box
- ccBBox bBox = obj->getBB_recursive();
- CCVector3 center = bBox.getCenter();
- PointCoordinateType diag = bBox.getDiagNorm();
- CCVector3d P = center;
- CCVector3d Pshift(0, 0, 0);
- double scale = 1.0;
- bool preserveCoordinateShift = true;
- //here we must test that coordinates are not too big whatever the case because OpenGL
- //really doesn't like big ones (even if we work with GLdoubles :( ).
- if (ccGlobalShiftManager::Handle( P,
- diag,
- ccGlobalShiftManager::DIALOG_IF_NECESSARY,
- false,
- Pshift,
- &preserveCoordinateShift,
- &scale)
- )
- {
- bool needRescale = (scale != 1.0);
- bool needShift = (Pshift.norm2() > 0);
- if (needRescale || needShift)
- {
- ccGLMatrix mat;
- mat.toIdentity();
- mat.data()[0] = mat.data()[5] = mat.data()[10] = static_cast<float>(scale);
- mat.setTranslation(Pshift);
- obj->applyGLTransformation_recursive(&mat);
- ccConsole::Warning(tr("Entity '%1' has been translated: (%2,%3,%4) and rescaled of a factor %5 [original position will be restored when saving]").arg(obj->getName()).arg(Pshift.x,0,'f',2).arg(Pshift.y,0,'f',2).arg(Pshift.z,0,'f',2).arg(scale,0,'f',6));
- }
- //update 'global shift' and 'global scale' for ALL clouds recursively
- if (preserveCoordinateShift)
- {
- //FIXME: why don't we do that all the time by the way?!
- ccHObject::Container children;
- children.push_back(obj);
- while (!children.empty())
- {
- ccHObject* child = children.back();
- children.pop_back();
- if (child->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccGenericPointCloud* pc = ccHObjectCaster::ToGenericPointCloud(child);
- pc->setGlobalShift(pc->getGlobalShift() + Pshift);
- pc->setGlobalScale(pc->getGlobalScale() * scale);
- }
- for (unsigned i = 0; i < child->getChildrenNumber(); ++i)
- {
- children.push_back(child->getChild(i));
- }
- }
- }
- }
- }
- //add object to DB root
- if (m_ccRoot)
- {
- //force a 'global zoom' if the DB was emtpy!
- if (!m_ccRoot->getRootEntity() || m_ccRoot->getRootEntity()->getChildrenNumber() == 0)
- {
- updateZoom = true;
- }
- m_ccRoot->addElement(obj, autoExpandDBTree);
- }
- else
- {
- ccLog::Warning(tr("[MainWindow::addToDB] Internal error: no associated DB?!"));
- assert(false);
- }
- //we can now set destination display (if none already)
- if (!obj->getDisplay())
- {
- ccGLWindowInterface* activeWin = getActiveGLWindow();
- if (!activeWin)
- {
- //no active GL window?!
- return;
- }
- obj->setDisplay_recursive(activeWin);
- }
- //eventually we update the corresponding display
- assert(obj->getDisplay());
- if (updateZoom)
- {
- static_cast<ccGLWindowInterface*>(obj->getDisplay())->zoomGlobal(); //automatically calls ccGLWindowInterface::redraw
- }
- else if (autoRedraw)
- {
- obj->redrawDisplay();
- }
- }
- void MainWindow::onExclusiveFullScreenToggled(bool state)
- {
- //we simply update the fullscreen action method icon (whatever the window)
- ccGLWindowInterface* win = getActiveGLWindow();
-
- if (win == nullptr)
- return;
- m_UI->actionExclusiveFullScreen->blockSignals(true);
- m_UI->actionExclusiveFullScreen->setChecked(win->exclusiveFullScreen());
- m_UI->actionExclusiveFullScreen->blockSignals(false);
- if ( !state
- && win->stereoModeIsEnabled()
- && ( win->getStereoParams().glassType == ccGLWindowInterface::StereoParams::NVIDIA_VISION
- || win->getStereoParams().glassType == ccGLWindowInterface::StereoParams::GENERIC_STEREO_DISPLAY ))
- {
- //auto disable stereo mode as NVidia Vision only works in full screen mode!
- m_UI->actionEnableStereo->setChecked(false);
- }
- }
- ccHObject* MainWindow::loadFile(QString filename, bool silent)
- {
- FileIOFilter::LoadParameters parameters;
- {
- parameters.alwaysDisplayLoadDialog = silent ? false : true;
- parameters.shiftHandlingMode = ccGlobalShiftManager::NO_DIALOG_AUTO_SHIFT;
- parameters.parentWidget = silent ? nullptr : this;
- }
- CC_FILE_ERROR result = CC_FERR_NO_ERROR;
- ccHObject* newGroup = FileIOFilter::LoadFromFile(filename, parameters, result);
- return newGroup;
- }
- void MainWindow::addToDBAuto(const QStringList& filenames)
- {
- ccGLWindowInterface* win = ccGLWindowInterface::FromEmitter(sender());
- if (win)
- {
- addToDB(filenames, QString(), win);
- }
- else
- {
- assert(false);
- }
- }
- void MainWindow::addToDB( const QStringList& filenames,
- QString fileFilter/*=QString()*/,
- ccGLWindowInterface* destWin/*=nullptr*/)
- {
- //to use the same 'global shift' for multiple files
- CCVector3d loadCoordinatesShift(0, 0, 0);
- bool loadCoordinatesTransEnabled = false;
- bool loadCoordinatesTransForced = false;
- FileIOFilter::LoadParameters parameters;
- {
- parameters.alwaysDisplayLoadDialog = true;
- parameters.shiftHandlingMode = ccGlobalShiftManager::DIALOG_IF_NECESSARY;
- parameters._coordinatesShift = &loadCoordinatesShift;
- parameters._coordinatesShiftEnabled = &loadCoordinatesTransEnabled;
- parameters._coordinatesShiftForced = &loadCoordinatesTransForced;
- parameters.parentWidget = this;
- }
- bool normalsDisplayedByDefault = ccOptions::Instance().normalsDisplayedByDefault;
- FileIOFilter::ResetSesionCounter();
- for ( const QString &filename : filenames )
- {
- CC_FILE_ERROR result = CC_FERR_NO_ERROR;
- ccHObject* newGroup = FileIOFilter::LoadFromFile(filename, parameters, result, fileFilter);
- if (newGroup)
- {
- if (!normalsDisplayedByDefault)
- {
- //disable the normals on all loaded clouds!
- ccHObject::Container clouds;
- newGroup->filterChildren(clouds, true, CC_TYPES::POINT_CLOUD);
- for (ccHObject* cloud : clouds)
- {
- if (cloud)
- {
- static_cast<ccGenericPointCloud*>(cloud)->showNormals(false);
- }
- }
- }
-
- if (destWin)
- {
- newGroup->setDisplay_recursive(destWin);
- }
- addToDB(newGroup, true, true, false);
- m_recentFiles->addFilePath( filename );
- }
- if (result == CC_FERR_CANCELED_BY_USER)
- {
- //stop importing the file if the user has cancelled the current process!
- break;
- }
- }
- QMainWindow::statusBar()->showMessage(tr("%1 file(s) loaded").arg(filenames.size()),2000);
- }
- void MainWindow::handleNewLabel(ccHObject* entity)
- {
- if (entity)
- {
- addToDB(entity, false, true, false, false);
- }
- else
- {
- assert(false);
- }
- }
- void MainWindow::forceConsoleDisplay()
- {
- //if the console is hidden, we autoamtically display it!
- if (m_UI->DockableConsole && m_UI->DockableConsole->isHidden())
- {
- m_UI->DockableConsole->show();
- QApplication::processEvents();
- }
- }
- ccColorScalesManager* MainWindow::getColorScalesManager()
- {
- return ccColorScalesManager::GetUniqueInstance();
- }
- void MainWindow::closeAll()
- {
- if (!m_ccRoot)
- {
- return;
- }
-
- QMessageBox message_box( QMessageBox::Question,
- tr("Close all"),
- tr("Are you sure you want to remove all loaded entities?"),
- QMessageBox::Yes | QMessageBox::No,
- this );
-
- if (message_box.exec() == QMessageBox::No)
- {
- return;
- }
-
- m_ccRoot->unloadAll();
- redrawAll(false);
- }
- void MainWindow::doActionLoadFile()
- {
- //persistent settings
- QSettings settings;
- settings.beginGroup(ccPS::LoadFile());
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- QString currentOpenDlgFilter = settings.value(ccPS::SelectedInputFilter(), BinFilter::GetFileFilter()).toString();
- // Add all available file I/O filters (with import capabilities)
- const QStringList filterStrings = FileIOFilter::ImportFilterList();
- const QString &allFilter = filterStrings.at( 0 );
-
- if ( !filterStrings.contains( currentOpenDlgFilter ) )
- {
- currentOpenDlgFilter = allFilter;
- }
-
- //file choosing dialog
- QStringList selectedFiles = QFileDialog::getOpenFileNames( this,
- tr("Open file(s)"),
- currentPath,
- filterStrings.join(s_fileFilterSeparator),
- ¤tOpenDlgFilter,
- CCFileDialogOptions());
- if (selectedFiles.isEmpty())
- return;
- //save last loading parameters
- currentPath = QFileInfo(selectedFiles[0]).absolutePath();
- settings.setValue(ccPS::CurrentPath(),currentPath);
- settings.setValue(ccPS::SelectedInputFilter(),currentOpenDlgFilter);
- settings.endGroup();
- if (currentOpenDlgFilter == allFilter)
- {
- currentOpenDlgFilter.clear(); //this way FileIOFilter will try to guess the file type automatically!
- }
-
- //load files
- addToDB(selectedFiles, currentOpenDlgFilter);
- }
- //Helper: check for a filename validity
- static bool IsValidFileName(QString filename)
- {
- #ifdef CC_WINDOWS
- QString sPattern("^(?!^(PRN|AUX|CLOCK\\$|NUL|CON|COM\\d|LPT\\d|\\..*)(\\..+)?$)[^\\x00-\\x1f\\\\?*:\\"";|/]+$");
- #else
- QString sPattern("^(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?""\\|<>\\. ](([^\\\\/:\\*\\?""\\|<>\\. ])|([^\\\\/:\\*\\?""\\|<>]*[^\\\\/:\\*\\?""\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?""\\|<>\\. ](([^\\\\/:\\*\\?""\\|<>\\. ])|([^\\\\/:\\*\\?""\\|<>]*[^\\\\/:\\*\\?""\\|<>\\. ]))?$");
- #endif
- return QRegExp(sPattern).exactMatch(filename);
- }
- void MainWindow::doActionSaveFile()
- {
- if (!haveSelection())
- return;
- ccHObject clouds("clouds");
- ccHObject meshes("meshes");
- ccHObject images("images");
- ccHObject polylines("polylines");
- ccHObject other("other");
- ccHObject otherSerializable("serializable");
- ccHObject::Container entitiesToDispatch;
- entitiesToDispatch.insert(entitiesToDispatch.begin(), m_selectedEntities.begin(), m_selectedEntities.end());
- ccHObject entitiesToSave;
- while (!entitiesToDispatch.empty())
- {
- ccHObject* child = entitiesToDispatch.back();
- entitiesToDispatch.pop_back();
- if (child->isA(CC_TYPES::HIERARCHY_OBJECT))
- {
- for (unsigned j = 0; j < child->getChildrenNumber(); ++j)
- entitiesToDispatch.push_back(child->getChild(j));
- }
- else
- {
- //we put the entity in the container corresponding to its type
- ccHObject* dest = nullptr;
- if (child->isA(CC_TYPES::POINT_CLOUD))
- dest = &clouds;
- else if (child->isKindOf(CC_TYPES::MESH))
- dest = &meshes;
- else if (child->isKindOf(CC_TYPES::IMAGE))
- dest = &images;
- else if (child->isKindOf(CC_TYPES::POLY_LINE))
- dest = &polylines;
- else if (child->isSerializable())
- dest = &otherSerializable;
- else
- dest = &other;
- assert(dest);
- //we don't want double insertions if the user has highlighted both the father and the child
- if (!dest->find(child->getUniqueID()))
- {
- dest->addChild(child, ccHObject::DP_NONE);
- entitiesToSave.addChild(child, ccHObject::DP_NONE);
- }
- }
- }
- bool hasCloud = (clouds.getChildrenNumber() != 0);
- bool hasMesh = (meshes.getChildrenNumber() != 0);
- bool hasImages = (images.getChildrenNumber() != 0);
- bool hasPolylines = (polylines.getChildrenNumber() != 0);
- bool hasSerializable = (otherSerializable.getChildrenNumber() != 0);
- bool hasOther = (other.getChildrenNumber() != 0);
- int stdSaveTypes = static_cast<int>(hasCloud)
- + static_cast<int>(hasMesh)
- + static_cast<int>(hasImages)
- + static_cast<int>(hasPolylines)
- + static_cast<int>(hasSerializable);
- if (stdSaveTypes == 0)
- {
- ccConsole::Error(tr("Can't save selected entity(ies) this way!"));
- return;
- }
- //we set up the right file filters, depending on the selected
- //entities type (cloud, mesh, etc.).
- QStringList fileFilters;
- {
- for ( const FileIOFilter::Shared& filter : FileIOFilter::GetFilters() )
- {
- bool atLeastOneExclusive = false;
- //can this filter export one or several clouds?
- bool canExportClouds = true;
- if (hasCloud)
- {
- bool isExclusive = true;
- bool multiple = false;
- canExportClouds = ( filter->canSave(CC_TYPES::POINT_CLOUD, multiple, isExclusive)
- && (multiple || clouds.getChildrenNumber() == 1) );
- atLeastOneExclusive |= isExclusive;
- }
- //can this filter export one or several meshes?
- bool canExportMeshes = true;
- if (hasMesh)
- {
- bool isExclusive = true;
- bool multiple = false;
- canExportMeshes = ( filter->canSave(CC_TYPES::MESH, multiple, isExclusive)
- && (multiple || meshes.getChildrenNumber() == 1) );
- atLeastOneExclusive |= isExclusive;
- }
- //can this filter export one or several polylines?
- bool canExportPolylines = true;
- if (hasPolylines)
- {
- bool isExclusive = true;
- bool multiple = false;
- canExportPolylines = ( filter->canSave(CC_TYPES::POLY_LINE, multiple, isExclusive)
- && (multiple || polylines.getChildrenNumber() == 1) );
- atLeastOneExclusive |= isExclusive;
- }
- //can this filter export one or several images?
- bool canExportImages = true;
- if (hasImages)
- {
- bool isExclusive = true;
- bool multiple = false;
- canExportImages = ( filter->canSave(CC_TYPES::IMAGE, multiple, isExclusive)
- && (multiple || images.getChildrenNumber() == 1) );
- atLeastOneExclusive |= isExclusive;
- }
- //can this filter export one or several other serializable entities?
- bool canExportSerializables = true;
- if (hasSerializable)
- {
- //check if all entities have the same type
- {
- CC_CLASS_ENUM firstClassID = otherSerializable.getChild(0)->getUniqueID();
- for (unsigned j = 1; j < otherSerializable.getChildrenNumber(); ++j)
- {
- if (otherSerializable.getChild(j)->getUniqueID() != firstClassID)
- {
- //we add a virtual second 'stdSaveType' so as to properly handle exlusivity
- ++stdSaveTypes;
- break;
- }
- }
- }
- for (unsigned j = 0; j < otherSerializable.getChildrenNumber(); ++j)
- {
- ccHObject* child = otherSerializable.getChild(j);
- bool isExclusive = true;
- bool multiple = false;
- canExportSerializables &= ( filter->canSave(child->getClassID(), multiple, isExclusive)
- && (multiple || otherSerializable.getChildrenNumber() == 1) );
- atLeastOneExclusive |= isExclusive;
- }
- }
- bool useThisFilter = canExportClouds
- && canExportMeshes
- && canExportImages
- && canExportPolylines
- && canExportSerializables
- && (!atLeastOneExclusive || stdSaveTypes == 1);
- if (useThisFilter)
- {
- QStringList ff = filter->getFileFilters(false);
- for (int j = 0; j < ff.size(); ++j)
- fileFilters.append(ff[j]);
- }
- }
- }
- //persistent settings
- QSettings settings;
- settings.beginGroup(ccPS::SaveFile());
- //default filter
- QString selectedFilter = fileFilters.first();
- if (hasCloud)
- selectedFilter = settings.value(ccPS::SelectedOutputFilterCloud(), selectedFilter).toString();
- else if (hasMesh)
- selectedFilter = settings.value(ccPS::SelectedOutputFilterMesh(), selectedFilter).toString();
- else if (hasImages)
- selectedFilter = settings.value(ccPS::SelectedOutputFilterImage(), selectedFilter).toString();
- else if (hasPolylines)
- selectedFilter = settings.value(ccPS::SelectedOutputFilterPoly(), selectedFilter).toString();
- //default output path (+ filename)
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- QString fullPathName = currentPath;
-
- if (haveOneSelection())
- {
- //hierarchy objects have generally as name: 'filename.ext (fullpath)'
- //so we must only take the first part! (otherwise this type of name
- //with a path inside disturbs QFileDialog a lot ;))
- QString defaultFileName(m_selectedEntities.front()->getName());
- if (m_selectedEntities.front()->isA(CC_TYPES::HIERARCHY_OBJECT))
- {
- QStringList parts = defaultFileName.split(' ', QString::SkipEmptyParts);
- if (!parts.empty())
- {
- defaultFileName = parts[0];
- }
- }
- //we remove the extension
- defaultFileName = QFileInfo(defaultFileName).completeBaseName();
- if (!IsValidFileName(defaultFileName))
- {
- ccLog::Warning(tr("[I/O] First entity's name would make an invalid filename! Can't use it..."));
- defaultFileName = "project";
- }
- fullPathName += QString("/") + defaultFileName;
- }
- //ask the user for the output filename
- QString selectedFilename = QFileDialog::getSaveFileName(this,
- tr("Save file"),
- fullPathName,
- fileFilters.join(s_fileFilterSeparator),
- &selectedFilter,
- CCFileDialogOptions());
- if (selectedFilename.isEmpty())
- {
- //process cancelled by the user
- return;
- }
- //ignored items
- if (hasOther)
- {
- ccConsole::Warning(tr("[I/O] The following selected entities won't be saved:"));
- for (unsigned i = 0; i < other.getChildrenNumber(); ++i)
- {
- ccConsole::Warning(QString("\t- %1s").arg(other.getChild(i)->getName()));
- }
- }
- CC_FILE_ERROR result = CC_FERR_NO_ERROR;
- FileIOFilter::SaveParameters parameters;
- {
- parameters.alwaysDisplaySaveDialog = true;
- parameters.parentWidget = this;
- }
- //specific case: BIN format
- if (selectedFilter == BinFilter::GetFileFilter())
- {
- if ( haveOneSelection() )
- {
- result = FileIOFilter::SaveToFile(m_selectedEntities.front(), selectedFilename, parameters, selectedFilter);
- }
- else
- {
- //we'll regroup all selected entities in a temporary group
- ccHObject tempContainer;
- ConvertToGroup(m_selectedEntities, tempContainer, ccHObject::DP_NONE);
- if (tempContainer.getChildrenNumber())
- {
- result = FileIOFilter::SaveToFile(&tempContainer, selectedFilename, parameters, selectedFilter);
- }
- else
- {
- ccLog::Warning(tr("[I/O] None of the selected entities can be saved this way..."));
- result = CC_FERR_NO_SAVE;
- }
- }
- }
- else if (entitiesToSave.getChildrenNumber() != 0)
- {
- //ignored items
- //if (hasSerializable)
- //{
- // if (!hasOther)
- // ccConsole::Warning(tr("[I/O] The following selected entites won't be saved:")); //display this warning only if not already done
- // for (unsigned i = 0; i < otherSerializable.getChildrenNumber(); ++i)
- // ccConsole::Warning(tr("\t- %1").arg(otherSerializable.getChild(i)->getName()));
- //}
- result = FileIOFilter::SaveToFile( entitiesToSave.getChildrenNumber() > 1 ? &entitiesToSave : entitiesToSave.getChild(0),
- selectedFilename,
- parameters,
- selectedFilter);
- if (result == CC_FERR_NO_ERROR && m_ccRoot)
- {
- m_ccRoot->unselectAllEntities();
- }
- }
- if (result == CC_FERR_NO_ERROR && selectedFilter == BinFilter::GetFileFilter())
- {
- //only for BIN files: display the compatible CC version
- short fileVersion = BinFilter::GetLastSavedFileVersion();
- if (0 != fileVersion)
- {
- QString minCCVersion = ccApplication::GetMinCCVersionForFileVersion(fileVersion);
- ccLog::Print(QString("This file can be loaded by CloudCompare version %1 and later").arg(minCCVersion));
- }
- }
- //update default filters
- if (hasCloud)
- settings.setValue(ccPS::SelectedOutputFilterCloud(),selectedFilter);
- if (hasMesh)
- settings.setValue(ccPS::SelectedOutputFilterMesh(), selectedFilter);
- if (hasImages)
- settings.setValue(ccPS::SelectedOutputFilterImage(),selectedFilter);
- if (hasPolylines)
- settings.setValue(ccPS::SelectedOutputFilterPoly(), selectedFilter);
- //we update current file path
- currentPath = QFileInfo(selectedFilename).absolutePath();
- settings.setValue(ccPS::CurrentPath(),currentPath);
- settings.endGroup();
- }
- void MainWindow::doActionSaveProject()
- {
- if (!m_ccRoot || !m_ccRoot->getRootEntity())
- {
- assert(false);
- return;
- }
- ccHObject* rootEntity = m_ccRoot->getRootEntity();
- if (rootEntity->getChildrenNumber() == 0)
- {
- return;
- }
- //default output path (+ filename)
- QSettings settings;
- settings.beginGroup(ccPS::SaveFile());
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- ccLog::PrintDebug(currentPath);
- QString fullPathName = currentPath;
- static QString s_previousProjectName{ "project" };
- QString defaultFileName = s_previousProjectName;
- if (rootEntity->getChildrenNumber() == 1)
- {
- // If there's only on top entity, we can try to use its name as the project name.
- ccHObject* topEntity = rootEntity->getChild(0);
- defaultFileName = topEntity->getName();
- if (topEntity->isA(CC_TYPES::HIERARCHY_OBJECT))
- {
- // Hierarchy objects have generally as name: 'filename.ext (fullpath)'
- // so we must only take the first part! (otherwise this type of name
- // with a path inside disturbs the QFileDialog a lot ;))
- QStringList parts = defaultFileName.split(' ', QString::SkipEmptyParts);
- if (!parts.empty())
- {
- defaultFileName = parts[0];
- }
- }
- //we remove the extension
- defaultFileName = QFileInfo(defaultFileName).completeBaseName();
- if (!IsValidFileName(defaultFileName))
- {
- ccLog::Warning(tr("[I/O] Top entity's name would make an invalid filename! Can't use it..."));
- defaultFileName = "project";
- }
- }
- fullPathName += QString("/") + defaultFileName;
- QString binFilter = BinFilter::GetFileFilter();
- //ask the user for the output filename
- QString selectedFilename = QFileDialog::getSaveFileName(this,
- tr("Save file"),
- fullPathName,
- binFilter,
- &binFilter,
- CCFileDialogOptions());
- if (selectedFilename.isEmpty())
- {
- //process cancelled by the user
- return;
- }
- FileIOFilter::SaveParameters parameters;
- {
- parameters.alwaysDisplaySaveDialog = true;
- parameters.parentWidget = this;
- }
- CC_FILE_ERROR result = FileIOFilter::SaveToFile(rootEntity->getChildrenNumber() == 1 ? rootEntity->getChild(0) : rootEntity, selectedFilename, parameters, binFilter);
- if (result == CC_FERR_NO_ERROR)
- {
- //only for BIN files: display the compatible CC version
- short fileVersion = BinFilter::GetLastSavedFileVersion();
- if (0 != fileVersion)
- {
- QString minCCVersion = ccApplication::GetMinCCVersionForFileVersion(fileVersion);
- ccLog::Print(QString("This file can be loaded by CloudCompare version %1 and later").arg(minCCVersion));
- }
- }
- //we update the current 'save' path
- QFileInfo fi(selectedFilename);
- s_previousProjectName = fi.fileName();
- currentPath = fi.absolutePath();
- settings.setValue(ccPS::CurrentPath(), currentPath);
- settings.endGroup();
- }
- void MainWindow::on3DViewActivated(QMdiSubWindow* mdiWin)
- {
- ccGLWindowInterface* win = mdiWin ? ccGLWindowInterface::FromWidget(mdiWin->widget()) : nullptr;
- if (win)
- {
- updateViewModePopUpMenu(win);
- updatePivotVisibilityPopUpMenu(win);
- m_UI->actionLockRotationAxis->blockSignals(true);
- m_UI->actionLockRotationAxis->setChecked(win->isRotationAxisLocked());
- m_UI->actionLockRotationAxis->blockSignals(false);
- m_UI->actionEnableStereo->blockSignals(true);
- m_UI->actionEnableStereo->setChecked(win->stereoModeIsEnabled());
- m_UI->actionEnableStereo->blockSignals(false);
- m_UI->actionExclusiveFullScreen->blockSignals(true);
- m_UI->actionExclusiveFullScreen->setChecked(win->exclusiveFullScreen());
- m_UI->actionExclusiveFullScreen->blockSignals(false);
- m_UI->actionShowCursor3DCoordinates->blockSignals(true);
- m_UI->actionShowCursor3DCoordinates->setChecked(win->cursorCoordinatesShown());
- m_UI->actionShowCursor3DCoordinates->blockSignals(false);
- m_UI->actionAutoPickRotationCenter->blockSignals(true);
- m_UI->actionAutoPickRotationCenter->setChecked(win->autoPickPivotAtCenter());
- m_UI->actionAutoPickRotationCenter->blockSignals(false);
- }
- m_UI->actionLockRotationAxis->setEnabled(win != nullptr);
- m_UI->actionEnableStereo->setEnabled(win != nullptr);
- m_UI->actionExclusiveFullScreen->setEnabled(win != nullptr);
- }
- void MainWindow::updateViewModePopUpMenu(ccGLWindowInterface* win)
- {
- if (!m_viewModePopupButton)
- return;
- //update the view mode pop-up 'top' icon
- if (win)
- {
- bool objectCentered = true;
- bool perspectiveEnabled = win->getPerspectiveState(objectCentered);
- QAction* currentModeAction = nullptr;
- if (!perspectiveEnabled)
- {
- currentModeAction = m_UI->actionSetOrthoView;
- }
- else if (objectCentered)
- {
- currentModeAction = m_UI->actionSetCenteredPerspectiveView;
- }
- else
- {
- currentModeAction = m_UI->actionSetViewerPerspectiveView;
- }
- assert(currentModeAction);
- m_viewModePopupButton->setIcon(currentModeAction->icon());
- m_viewModePopupButton->setEnabled(true);
- }
- else
- {
- m_viewModePopupButton->setIcon(QIcon());
- m_viewModePopupButton->setEnabled(false);
- }
- }
- void MainWindow::updatePivotVisibilityPopUpMenu(ccGLWindowInterface* win)
- {
- if (!m_pivotVisibilityPopupButton)
- return;
- //update the pivot visibility pop-up 'top' icon
- if (win)
- {
- QAction* visibilityAction = nullptr;
- switch(win->getPivotVisibility())
- {
- case ccGLWindowInterface::PIVOT_HIDE:
- visibilityAction = m_UI->actionSetPivotOff;
- break;
- case ccGLWindowInterface::PIVOT_SHOW_ON_MOVE:
- visibilityAction = m_UI->actionSetPivotRotationOnly;
- break;
- case ccGLWindowInterface::PIVOT_ALWAYS_SHOW:
- visibilityAction = m_UI->actionSetPivotAlwaysOn;
- break;
- default:
- assert(false);
- }
- if (visibilityAction)
- m_pivotVisibilityPopupButton->setIcon(visibilityAction->icon());
- //pivot is not available in viewer-based perspective!
- bool objectCentered = true;
- win->getPerspectiveState(objectCentered);
- m_pivotVisibilityPopupButton->setEnabled(objectCentered);
- }
- else
- {
- m_pivotVisibilityPopupButton->setIcon(QIcon());
- m_pivotVisibilityPopupButton->setEnabled(false);
- }
- }
- void MainWindow::updateMenus()
- {
- ccGLWindowInterface* active3DView = getActiveGLWindow();
- bool hasMdiChild = (active3DView != nullptr);
- int mdiChildCount = getGLWindowCount();
- bool hasLoadedEntities = (m_ccRoot && m_ccRoot->getRootEntity() && m_ccRoot->getRootEntity()->getChildrenNumber() != 0);
- bool hasSelectedEntities = (m_ccRoot && m_ccRoot->countSelectedEntities() > 0);
- //General Menu
- m_UI->menuEdit->setEnabled(true/*hasSelectedEntities*/);
- m_UI->menuTools->setEnabled(true/*hasSelectedEntities*/);
- //3D Views Menu
- m_UI->actionClose3DView ->setEnabled(hasMdiChild);
- m_UI->actionCloseAll3DViews->setEnabled(mdiChildCount != 0);
- m_UI->actionTile3DViews ->setEnabled(mdiChildCount > 1);
- m_UI->actionCascade3DViews ->setEnabled(mdiChildCount > 1);
- m_UI->actionNext3DView ->setEnabled(mdiChildCount > 1);
- m_UI->actionPrevious3DView ->setEnabled(mdiChildCount > 1);
- //Shaders & Filters display Menu
- bool shadersEnabled = (active3DView ? active3DView->areShadersEnabled() : false);
- m_UI->actionLoadShader->setEnabled(shadersEnabled);
- m_UI->actionDeleteShader->setEnabled(shadersEnabled);
- //View Menu
- m_UI->toolBarView->setEnabled(hasMdiChild);
- //oher actions
- m_UI->actionSegment->setEnabled(hasMdiChild && hasSelectedEntities);
- m_UI->actionTranslateRotate->setEnabled(hasMdiChild && hasSelectedEntities);
- m_UI->actionPointPicking->setEnabled(hasMdiChild && hasLoadedEntities);
- m_UI->actionTestFrameRate->setEnabled(hasMdiChild);
- m_UI->actionRenderToFile->setEnabled(hasMdiChild);
- m_UI->actionToggleSunLight->setEnabled(hasMdiChild);
- m_UI->actionToggleCustomLight->setEnabled(hasMdiChild);
- m_UI->actionToggleCenteredPerspective->setEnabled(hasMdiChild);
- m_UI->actionToggleViewerBasedPerspective->setEnabled(hasMdiChild);
- //plugins
- m_pluginUIManager->updateMenus();
- }
- void MainWindow::update3DViewsMenu()
- {
- m_UI->menu3DViews->clear();
- m_UI->menu3DViews->addAction(m_UI->actionNew3DView);
- m_UI->menu3DViews->addSeparator();
- m_UI->menu3DViews->addAction(m_UI->actionZoomIn);
- m_UI->menu3DViews->addAction(m_UI->actionZoomOut);
- m_UI->menu3DViews->addSeparator();
- m_UI->menu3DViews->addAction(m_UI->actionClose3DView);
- m_UI->menu3DViews->addAction(m_UI->actionCloseAll3DViews);
- m_UI->menu3DViews->addSeparator();
- m_UI->menu3DViews->addAction(m_UI->actionTile3DViews);
- m_UI->menu3DViews->addAction(m_UI->actionCascade3DViews);
- m_UI->menu3DViews->addSeparator();
- m_UI->menu3DViews->addAction(m_UI->actionNext3DView);
- m_UI->menu3DViews->addAction(m_UI->actionPrevious3DView);
- QList<QMdiSubWindow *> windows = m_mdiArea->subWindowList();
- if (!windows.isEmpty())
- {
- //Dynamic Separator
- QAction* separator = new QAction(this);
- separator->setSeparator(true);
- m_UI->menu3DViews->addAction(separator);
- int i = 0;
-
- for ( QMdiSubWindow* window : windows )
- {
- ccGLWindowInterface *child = ccGLWindowInterface::FromWidget(window->widget());
- QString text = QString("&%1 %2").arg(++i).arg(child->getWindowTitle());
- QAction *action = m_UI->menu3DViews->addAction(text);
-
- action->setCheckable(true);
- action->setChecked(child == getActiveGLWindow());
-
- connect(action, &QAction::triggered, this, [=] () {
- setActiveSubWindow( window );
- } );
- }
- }
- }
- void MainWindow::setActiveSubWindow(QWidget *window)
- {
- if (!window || !m_mdiArea)
- return;
- m_mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow*>(window));
- }
- void MainWindow::redrawAll(bool only2D/*=false*/)
- {
- for (QMdiSubWindow* window : m_mdiArea->subWindowList())
- {
- ccGLWindowInterface::FromWidget(window->widget())->redraw(only2D);
- }
- }
- void MainWindow::refreshAll(bool only2D/*=false*/)
- {
- for (QMdiSubWindow* window : m_mdiArea->subWindowList())
- {
- ccGLWindowInterface::FromWidget(window->widget())->refresh(only2D);
- }
- }
- void MainWindow::updateUI()
- {
- updateUIWithSelection();
- updateMenus();
- updatePropertiesView();
- }
- void MainWindow::updatePropertiesView()
- {
- if (m_ccRoot)
- {
- m_ccRoot->updatePropertiesView();
- }
- }
- void MainWindow::updateUIWithSelection()
- {
- dbTreeSelectionInfo selInfo;
- m_selectedEntities.clear();
- if (m_ccRoot)
- {
- m_ccRoot->getSelectedEntities(m_selectedEntities, CC_TYPES::OBJECT, &selInfo);
- }
- enableUIItems(selInfo);
- }
- void MainWindow::enableAll()
- {
- for ( QMdiSubWindow* window : m_mdiArea->subWindowList() )
- {
- window->setEnabled( true );
- }
- }
- void MainWindow::disableAll()
- {
- for ( QMdiSubWindow* window : m_mdiArea->subWindowList() )
- {
- window->setEnabled( false );
- }
- }
- void MainWindow::disableAllBut(ccGLWindowInterface* win)
- {
- //we disable all other windows
- for ( QMdiSubWindow* window : m_mdiArea->subWindowList() )
- {
- if (ccGLWindowInterface::FromWidget(window->widget()) != win)
- {
- window->setEnabled(false);
- }
- }
- }
- void MainWindow::enableUIItems(dbTreeSelectionInfo& selInfo)
- {
- bool dbIsEmpty = (!m_ccRoot || !m_ccRoot->getRootEntity() || m_ccRoot->getRootEntity()->getChildrenNumber() == 0);
- bool atLeastOneEntity = (selInfo.selCount > 0);
- bool atLeastOneCloud = (selInfo.cloudCount > 0);
- bool atLeastOneMesh = (selInfo.meshCount > 0);
- bool atLeastOnePrimitive = (selInfo.primitiveCount > 0);
- //bool atLeastOneOctree = (selInfo.octreeCount > 0);
- bool atLeastOneNormal = (selInfo.normalsCount > 0);
- bool atLeastOneColor = (selInfo.colorCount > 0);
- bool atLeastOneSF = (selInfo.sfCount > 0);
- bool atLeastOneGrid = (selInfo.gridCound > 0);
- //bool atLeastOneSensor = (selInfo.sensorCount > 0);
- bool atLeastOneGBLSensor = (selInfo.gblSensorCount > 0);
- bool atLeastOneCameraSensor = (selInfo.cameraSensorCount > 0);
- bool atLeastOnePolyline = (selInfo.polylineCount > 0);
- bool activeWindow = (getActiveGLWindow() != nullptr);
- //menuEdit->setEnabled(atLeastOneEntity);
- //menuTools->setEnabled(atLeastOneEntity);
- m_UI->actionTracePolyline->setEnabled(!dbIsEmpty);
- m_UI->actionZoomAndCenter->setEnabled(atLeastOneEntity && activeWindow);
- m_UI->actionSave->setEnabled(atLeastOneEntity);
- m_UI->actionSaveProject->setEnabled(!dbIsEmpty);
- m_UI->actionClone->setEnabled(atLeastOneEntity);
- m_UI->actionDelete->setEnabled(atLeastOneEntity);
- m_UI->actionExportCoordToSF->setEnabled(atLeastOneEntity);
- m_UI->actionExportNormalToSF->setEnabled(atLeastOneNormal);
- m_UI->actionSegment->setEnabled(atLeastOneEntity && activeWindow);
- m_UI->actionTranslateRotate->setEnabled(atLeastOneEntity && activeWindow);
- m_UI->actionShowDepthBuffer->setEnabled(atLeastOneGBLSensor);
- m_UI->actionExportDepthBuffer->setEnabled(atLeastOneGBLSensor);
- m_UI->actionComputePointsVisibility->setEnabled(atLeastOneGBLSensor);
- m_UI->actionResampleWithOctree->setEnabled(atLeastOneCloud);
- m_UI->actionApplyScale->setEnabled(atLeastOneCloud || atLeastOneMesh || atLeastOnePolyline);
- m_UI->actionApplyTransformation->setEnabled(atLeastOneEntity);
- m_UI->actionComputeOctree->setEnabled(atLeastOneCloud || atLeastOneMesh);
- m_UI->actionComputeNormals->setEnabled(atLeastOneCloud || atLeastOneMesh);
- m_UI->actionChangeColorLevels->setEnabled(atLeastOneCloud || atLeastOneMesh);
- m_UI->actionEditGlobalShiftAndScale->setEnabled(atLeastOneCloud || atLeastOneMesh || atLeastOnePolyline);
- m_UI->actionCrop->setEnabled(atLeastOneCloud || atLeastOneMesh);
- m_UI->actionSetUniqueColor->setEnabled(atLeastOneEntity/*atLeastOneCloud || atLeastOneMesh*/); //DGM: we can set color to a group now!
- m_UI->actionSetColorGradient->setEnabled(atLeastOneCloud || atLeastOneMesh);
- m_UI->actionColorize->setEnabled(atLeastOneEntity/*atLeastOneCloud || atLeastOneMesh*/); //DGM: we can set color to a group now!
- m_UI->actionDeleteScanGrid->setEnabled(atLeastOneGrid);
- m_UI->actionScalarFieldFromColor->setEnabled(atLeastOneEntity && atLeastOneColor);
- m_UI->actionComputeMeshAA->setEnabled(atLeastOneCloud);
- m_UI->actionComputeMeshLS->setEnabled(atLeastOneCloud);
- m_UI->actionMeshScanGrids->setEnabled(atLeastOneGrid);
- //actionComputeQuadric3D->setEnabled(atLeastOneCloud);
- m_UI->actionComputeBestFitBB->setEnabled(atLeastOneEntity);
- m_UI->actionComputeGeometricFeature->setEnabled(atLeastOneCloud);
- m_UI->actionRemoveDuplicatePoints->setEnabled(atLeastOneCloud);
- m_UI->actionFitPlane->setEnabled(atLeastOneEntity);
- m_UI->actionFitPlaneProxy->setEnabled(atLeastOneEntity);
- m_UI->actionFitSphere->setEnabled(atLeastOneCloud);
- m_UI->actionFitCircle->setEnabled(atLeastOneCloud);
- m_UI->actionLevel->setEnabled(atLeastOneEntity);
- m_UI->actionFitFacet->setEnabled(atLeastOneEntity);
- m_UI->actionFitQuadric->setEnabled(atLeastOneCloud);
- m_UI->actionSubsample->setEnabled(atLeastOneCloud);
- m_UI->actionSNETest->setEnabled(atLeastOneCloud);
- m_UI->actionExportCloudInfo->setEnabled(atLeastOneEntity);
- m_UI->actionExportPlaneInfo->setEnabled(atLeastOneEntity);
- m_UI->actionFilterByValue->setEnabled(atLeastOneSF);
- m_UI->actionConvertToRGB->setEnabled(atLeastOneSF);
- m_UI->actionConvertToRandomRGB->setEnabled(atLeastOneSF);
- m_UI->actionRenameSF->setEnabled(atLeastOneSF);
- m_UI->actionAddIdField->setEnabled(atLeastOneCloud);
- m_UI->actionSplitCloudUsingSF->setEnabled(atLeastOneSF);
- m_UI->actionComputeStatParams->setEnabled(atLeastOneSF);
- m_UI->actionComputeStatParams2->setEnabled(atLeastOneSF);
- m_UI->actionShowHistogram->setEnabled(atLeastOneSF);
- m_UI->actionGaussianFilter->setEnabled(atLeastOneSF);
- m_UI->actionBilateralFilter->setEnabled(atLeastOneSF);
- m_UI->actionDeleteScalarField->setEnabled(atLeastOneSF);
- m_UI->actionDeleteAllSF->setEnabled(atLeastOneSF);
- m_UI->actionMultiplySF->setEnabled(/*TODO: atLeastOneSF*/false);
- m_UI->actionSFGradient->setEnabled(atLeastOneSF);
- m_UI->actionSetSFAsCoord->setEnabled(atLeastOneSF && atLeastOneCloud);
- m_UI->actionInterpolateSFs->setEnabled(atLeastOneCloud || atLeastOneMesh);
- m_UI->actionSamplePointsOnMesh->setEnabled(atLeastOneMesh);
- m_UI->actionMeasureMeshSurface->setEnabled(atLeastOneMesh);
- m_UI->actionMeasureMeshVolume->setEnabled(atLeastOneMesh);
- m_UI->actionFlagMeshVertices->setEnabled(atLeastOneMesh);
- m_UI->actionSmoothMeshLaplacian->setEnabled(atLeastOneMesh);
- m_UI->actionConvertTextureToColor->setEnabled(atLeastOneMesh);
- m_UI->actionSubdivideMesh->setEnabled(atLeastOneMesh);
- m_UI->actionFlipMeshTriangles->setEnabled(atLeastOneMesh);
- m_UI->actionDistanceToBestFitQuadric3D->setEnabled(atLeastOneCloud);
- m_UI->actionDistanceMap->setEnabled(atLeastOneMesh || atLeastOneCloud);
- m_UI->menuMeshScalarField->setEnabled(atLeastOneSF && atLeastOneMesh);
- //actionSmoothMeshSF->setEnabled(atLeastOneSF && atLeastOneMesh);
- //actionEnhanceMeshSF->setEnabled(atLeastOneSF && atLeastOneMesh);
- m_UI->actionOrientNormalsMST->setEnabled(atLeastOneCloud && atLeastOneNormal);
- m_UI->actionOrientNormalsFM->setEnabled(atLeastOneCloud && atLeastOneNormal);
- m_UI->actionShiftPointsAlongNormals->setEnabled(atLeastOneCloud && atLeastOneNormal);
- m_UI->actionClearNormals->setEnabled(atLeastOneNormal);
- m_UI->actionInvertNormals->setEnabled(atLeastOneNormal);
- m_UI->actionConvertNormalToHSV->setEnabled(atLeastOneNormal);
- m_UI->actionConvertNormalToDipDir->setEnabled(atLeastOneNormal);
- m_UI->actionClearColor->setEnabled(atLeastOneColor);
- m_UI->actionRGBToGreyScale->setEnabled(atLeastOneColor);
- m_UI->actionEnhanceRGBWithIntensities->setEnabled(atLeastOneColor);
- m_UI->actionRGBGaussianFilter->setEnabled(atLeastOneColor);
- m_UI->actionRGBBilateralFilter->setEnabled(atLeastOneColor);
- m_UI->actionRGBMeanFilter->setEnabled(atLeastOneColor);
- m_UI->actionRGBMedianFilter->setEnabled(atLeastOneColor);
- m_UI->actionColorFromScalarField->setEnabled(atLeastOneSF);
- // == 1
- bool exactlyOneEntity = (selInfo.selCount == 1);
- bool exactlyOneGroup = (selInfo.groupCount == 1);
- bool exactlyOneCloud = (selInfo.cloudCount == 1);
- bool exactlyOneMesh = (selInfo.meshCount == 1);
- bool exactlyOneSF = (selInfo.sfCount == 1);
- bool exactlyOneSensor = (selInfo.sensorCount == 1);
- bool exactlyOneCameraSensor = (selInfo.cameraSensorCount == 1);
- m_UI->actionConvertPolylinesToMesh->setEnabled(atLeastOnePolyline || exactlyOneGroup);
- m_UI->actionSamplePointsOnPolyline->setEnabled(atLeastOnePolyline);
- m_UI->actionSmoothPolyline->setEnabled(atLeastOnePolyline);
- m_UI->actionMeshTwoPolylines->setEnabled(selInfo.selCount == 2 && selInfo.polylineCount == 2);
- m_UI->actionCreateSurfaceBetweenTwoPolylines->setEnabled(m_UI->actionMeshTwoPolylines->isEnabled()); //clone of actionMeshTwoPolylines
- m_UI->actionModifySensor->setEnabled(exactlyOneSensor);
- m_UI->actionComputeDistancesFromSensor->setEnabled(atLeastOneCameraSensor || atLeastOneGBLSensor);
- m_UI->actionComputeScatteringAngles->setEnabled(exactlyOneSensor);
- m_UI->actionViewFromSensor->setEnabled(exactlyOneSensor);
- m_UI->actionCreateGBLSensor->setEnabled(atLeastOneCloud);
- m_UI->actionCreateCameraSensor->setEnabled(selInfo.selCount <= 1); //free now
- m_UI->actionProjectUncertainty->setEnabled(exactlyOneCameraSensor);
- m_UI->actionCheckPointsInsideFrustum->setEnabled(exactlyOneCameraSensor);
- m_UI->actionLabelConnectedComponents->setEnabled(atLeastOneCloud);
- m_UI->actionSORFilter->setEnabled(atLeastOneCloud);
- m_UI->actionNoiseFilter->setEnabled(atLeastOneCloud);
- m_UI->actionUnroll->setEnabled(exactlyOneEntity);
- m_UI->actionStatisticalTest->setEnabled(exactlyOneEntity && exactlyOneSF);
- m_UI->actionAddConstantSF->setEnabled(exactlyOneCloud || exactlyOneMesh);
- m_UI->actionAddClassificationSF->setEnabled(exactlyOneCloud || exactlyOneMesh);
- m_UI->actionEditGlobalScale->setEnabled(exactlyOneCloud || exactlyOneMesh);
- m_UI->actionComputeKdTree->setEnabled(exactlyOneCloud || exactlyOneMesh);
- m_UI->actionSetSFsAsNormal->setEnabled(exactlyOneCloud || exactlyOneMesh);
- m_UI->actionShowWaveDialog->setEnabled(exactlyOneCloud);
- m_UI->actionCompressFWFData->setEnabled(atLeastOneCloud);
- m_UI->actionKMeans->setEnabled(/*TODO: exactlyOneEntity && exactlyOneSF*/false);
- m_UI->actionFrontPropagation->setEnabled(/*TODO: exactlyOneEntity && exactlyOneSF*/false);
- //actionCreatePlane->setEnabled(true);
- m_UI->actionEditPlane->setEnabled(selInfo.planeCount == 1);
- m_UI->actionFlipPlane->setEnabled(selInfo.planeCount != 0);
- m_UI->actionComparePlanes->setEnabled(selInfo.planeCount == 2);
- m_UI->actionFindBiggestInnerRectangle->setEnabled(exactlyOneCloud);
- m_UI->menuActiveScalarField->setEnabled((exactlyOneCloud || exactlyOneMesh) && selInfo.sfCount > 0);
- m_UI->actionCrossSection->setEnabled(atLeastOneCloud || atLeastOneMesh || (selInfo.groupCount != 0));
- m_UI->actionExtractSections->setEnabled(atLeastOneCloud);
- m_UI->actionRasterize->setEnabled(exactlyOneCloud);
- m_UI->actionCompute2HalfDimVolume->setEnabled(selInfo.cloudCount == selInfo.selCount && selInfo.cloudCount >= 1 && selInfo.cloudCount <= 2); //one or two clouds!
- m_UI->actionPointListPicking->setEnabled(exactlyOneCloud || exactlyOneMesh);
- // == 2
- bool exactlyTwoEntities = (selInfo.selCount == 2);
- bool exactlyTwoClouds = (selInfo.cloudCount == 2);
- //bool exactlyTwoSF = (selInfo.sfCount == 2);
- m_UI->actionRegister->setEnabled(exactlyTwoEntities);
- m_UI->actionInterpolateColors->setEnabled(exactlyTwoEntities && atLeastOneColor);
- m_UI->actionPointPairsAlign->setEnabled(atLeastOneEntity);
- m_UI->actionBBCenterToOrigin->setEnabled(atLeastOneEntity);
- m_UI->actionBBMinCornerToOrigin->setEnabled(atLeastOneEntity);
- m_UI->actionBBMaxCornerToOrigin->setEnabled(atLeastOneEntity);
- m_UI->actionAlign->setEnabled(exactlyTwoEntities); //Aurelien BEY le 13/11/2008
- m_UI->actionCloudCloudDist->setEnabled(exactlyTwoClouds);
- m_UI->actionCloudMeshDist->setEnabled(exactlyTwoEntities && atLeastOneMesh);
- m_UI->actionCloudPrimitiveDist->setEnabled(atLeastOneCloud && (atLeastOnePrimitive || atLeastOnePolyline));
- m_UI->actionCPS->setEnabled(exactlyTwoClouds);
- m_UI->actionScalarFieldArithmetic->setEnabled(exactlyOneEntity && atLeastOneSF);
- //>1
- bool atLeastTwoEntities = (selInfo.selCount > 1);
- m_UI->actionMerge->setEnabled(atLeastTwoEntities);
- m_UI->actionMatchBBCenters->setEnabled(atLeastTwoEntities);
- m_UI->actionMatchScales->setEnabled(atLeastTwoEntities);
- //standard plugins
- m_pluginUIManager->handleSelectionChanged();
- }
- void MainWindow::echoMouseWheelRotate(float wheelDelta_deg)
- {
- if (!m_UI->actionEnableCameraLink->isChecked())
- return;
- ccGLWindowInterface* sendingWindow = ccGLWindowInterface::FromEmitter(sender());
- if (!sendingWindow)
- return;
- for ( QMdiSubWindow* window : m_mdiArea->subWindowList() )
- {
- ccGLWindowInterface* child = ccGLWindowInterface::FromWidget(window->widget());
- if (child != sendingWindow)
- {
- child->signalEmitter()->blockSignals(true);
- child->onWheelEvent(wheelDelta_deg);
- child->signalEmitter()->blockSignals(false);
- child->redraw();
- }
- }
- }
- void MainWindow::echoBaseViewMatRotation(const ccGLMatrixd& rotMat)
- {
- if (!m_UI->actionEnableCameraLink->isChecked())
- return;
- ccGLWindowInterface* sendingWindow = ccGLWindowInterface::FromEmitter(sender());
- if (!sendingWindow)
- return;
- for ( QMdiSubWindow* window : m_mdiArea->subWindowList() )
- {
- ccGLWindowInterface* child = ccGLWindowInterface::FromWidget(window->widget());
- if (child != sendingWindow)
- {
- child->signalEmitter()->blockSignals(true);
- child->rotateBaseViewMat(rotMat);
- child->signalEmitter()->blockSignals(false);
- child->redraw();
- }
- }
- }
- void MainWindow::echoCameraPosChanged(const CCVector3d& P)
- {
- if (!m_UI->actionEnableCameraLink->isChecked())
- return;
- ccGLWindowInterface* sendingWindow = ccGLWindowInterface::FromEmitter(sender());
- if (!sendingWindow)
- return;
- for ( QMdiSubWindow* window : m_mdiArea->subWindowList() )
- {
- ccGLWindowInterface* child = ccGLWindowInterface::FromWidget(window->widget());
- if (child != sendingWindow)
- {
- child->signalEmitter()->blockSignals(true);
- child->setCameraPos(P);
- child->signalEmitter()->blockSignals(false);
- child->redraw();
- }
- }
- }
- void MainWindow::echoPivotPointChanged(const CCVector3d& P)
- {
- if (!m_UI->actionEnableCameraLink->isChecked())
- return;
- ccGLWindowInterface* sendingWindow = ccGLWindowInterface::FromEmitter(sender());
- if (!sendingWindow)
- return;
- for ( QMdiSubWindow* window : m_mdiArea->subWindowList() )
- {
- ccGLWindowInterface* child = ccGLWindowInterface::FromWidget(window->widget());
- if (child != sendingWindow)
- {
- child->signalEmitter()->blockSignals(true);
- child->setPivotPoint(P);
- child->signalEmitter()->blockSignals(false);
- child->redraw();
- }
- }
- }
- void MainWindow::dispToConsole(QString message, ConsoleMessageLevel level/*=STD_CONSOLE_MESSAGE*/)
- {
- switch(level)
- {
- case STD_CONSOLE_MESSAGE:
- ccConsole::Print(message);
- break;
- case WRN_CONSOLE_MESSAGE:
- ccConsole::Warning(message);
- break;
- case ERR_CONSOLE_MESSAGE:
- ccConsole::Error(message);
- break;
- }
- }
- void MainWindow::doActionLoadShader() //TODO
- {
- ccConsole::Error(tr("Not yet implemented! Sorry ..."));
- }
- void MainWindow::doActionKMeans()//TODO
- {
- ccConsole::Error(tr("Not yet implemented! Sorry ..."));
- }
- void MainWindow::doActionFrontPropagation() //TODO
- {
- ccConsole::Error(tr("Not yet implemented! Sorry ..."));
- }
- /************** STATIC METHODS ******************/
- MainWindow* MainWindow::TheInstance()
- {
- if (!s_instance)
- s_instance = new MainWindow();
- return s_instance;
- }
- void MainWindow::DestroyInstance()
- {
- delete s_instance;
- s_instance=nullptr;
- }
- void MainWindow::GetGLWindows(std::vector<ccGLWindowInterface*>& glWindows)
- {
- const QList<QMdiSubWindow*> windows = TheInstance()->m_mdiArea->subWindowList();
- if ( windows.empty() )
- return;
- glWindows.clear();
- glWindows.reserve( windows.size() );
- for ( QMdiSubWindow* window : windows )
- {
- glWindows.push_back(ccGLWindowInterface::FromWidget(window->widget()));
- }
- }
- ccGLWindowInterface* MainWindow::GetActiveGLWindow()
- {
- return TheInstance()->getActiveGLWindow();
- }
- ccGLWindowInterface* MainWindow::GetGLWindow(const QString& title)
- {
- const QList<QMdiSubWindow *> windows = TheInstance()->m_mdiArea->subWindowList();
- if ( windows.empty() )
- return nullptr;
- for ( QMdiSubWindow* window : windows )
- {
- ccGLWindowInterface* win = ccGLWindowInterface::FromWidget(window->widget());
- if (win->getWindowTitle() == title)
- return win;
- }
- return nullptr;
- }
- void MainWindow::RefreshAllGLWindow(bool only2D/*=false*/)
- {
- TheInstance()->refreshAll(only2D);
- }
- void MainWindow::UpdateUI()
- {
- TheInstance()->updateUI();
- }
- ccDBRoot* MainWindow::db()
- {
- return m_ccRoot;
- }
- void MainWindow::addEditPlaneAction( QMenu &menu ) const
- {
- menu.addAction( m_UI->actionEditPlane );
- }
- ccHObject* MainWindow::dbRootObject()
- {
- return (m_ccRoot ? m_ccRoot->getRootEntity() : nullptr);
- }
- ccUniqueIDGenerator::Shared MainWindow::getUniqueIDGenerator()
- {
- return ccObject::GetUniqueIDGenerator();
- }
- void MainWindow::createGLWindow(ccGLWindowInterface*& window, QWidget*& widget) const
- {
- bool stereoMode = ccGLWindowInterface::TestStereoSupport();
- ccGLWindowInterface::Create(window, widget, stereoMode);
- assert(window && widget);
- }
- void MainWindow::destroyGLWindow(ccGLWindowInterface* view3D) const
- {
- if (view3D)
- {
- view3D->asQObject()->setParent(nullptr);
- delete view3D;
- }
- }
- ccMainAppInterface::ccHObjectContext MainWindow::removeObjectTemporarilyFromDBTree(ccHObject* obj)
- {
- ccHObjectContext context;
- assert(obj);
- if (!m_ccRoot || !obj)
- return context;
- //mandatory (to call putObjectBackIntoDBTree)
- context.parent = obj->getParent();
- //remove the object's dependency to its father (in case it undergoes 'severe' modifications)
- if (context.parent)
- {
- context.parentFlags = context.parent->getDependencyFlagsWith(obj);
- context.childFlags = obj->getDependencyFlagsWith(context.parent);
- context.parent->removeDependencyWith(obj);
- obj->removeDependencyWith(context.parent);
- }
- m_ccRoot->removeElement(obj);
- return context;
- }
- void MainWindow::putObjectBackIntoDBTree(ccHObject* obj, const ccHObjectContext& context)
- {
- assert(obj);
- if (!obj || !m_ccRoot)
- return;
- if (context.parent)
- {
- context.parent->addChild(obj,context.parentFlags);
- obj->addDependency(context.parent,context.childFlags);
- }
- //DGM: we must call 'notifyGeometryUpdate' as any call to this method
- //while the object was temporarily 'cut' from the DB tree were
- //ineffective!
- obj->notifyGeometryUpdate();
- m_ccRoot->addElement(obj,false);
- }
- void MainWindow::doActionGlobalShiftSeetings()
- {
- QDialog dialog(this);
- Ui_GlobalShiftSettingsDialog ui;
- ui.setupUi(&dialog);
- ui.maxAbsCoordSpinBox->setValue(static_cast<int>(log10(ccGlobalShiftManager::MaxCoordinateAbsValue())));
- ui.maxAbsDiagSpinBox->setValue(static_cast<int>(log10(ccGlobalShiftManager::MaxBoundgBoxDiagonal())));
- if (!dialog.exec())
- {
- return;
- }
- double maxAbsCoord = pow(10.0, static_cast<double>(ui.maxAbsCoordSpinBox->value()));
- double maxAbsDiag = pow(10.0, static_cast<double>(ui.maxAbsDiagSpinBox->value()));
- ccGlobalShiftManager::SetMaxCoordinateAbsValue(maxAbsCoord);
- ccGlobalShiftManager::SetMaxBoundgBoxDiagonal(maxAbsDiag);
- ccLog::Print(tr("[Global Shift] Max abs. coord = %1 / max abs. diag = %2")
- .arg(ccGlobalShiftManager::MaxCoordinateAbsValue(), 0, 'e', 0)
- .arg(ccGlobalShiftManager::MaxBoundgBoxDiagonal(), 0, 'e', 0));
- //save to persistent settings
- {
- QSettings settings;
- settings.beginGroup(ccPS::GlobalShift());
- settings.setValue(ccPS::MaxAbsCoord(), maxAbsCoord);
- settings.setValue(ccPS::MaxAbsDiag(), maxAbsDiag);
- settings.endGroup();
- }
- }
- void MainWindow::doActionCompressFWFData()
- {
- for ( ccHObject *entity : getSelectedEntities() )
- {
- if (!entity || !entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- continue;
- }
- ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
- cloud->compressFWFData();
- }
- }
- void MainWindow::doActionShowWaveDialog()
- {
- if (!haveSelection())
- return;
- ccHObject* entity = haveOneSelection() ? m_selectedEntities.front() : nullptr;
- if (!entity || !entity->isKindOf(CC_TYPES::POINT_CLOUD))
- {
- ccConsole::Error(tr("Select one point cloud!"));
- return;
- }
- ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
- if (!cloud->hasFWF())
- {
- ccConsole::Error(tr("Cloud has no associated waveform information"));
- return;
- }
- ccWaveDialog* wDlg = new ccWaveDialog(cloud, m_pickingHub, this);
- wDlg->setAttribute(Qt::WA_DeleteOnClose);
- wDlg->setModal(false);
- wDlg->show();
- }
- void MainWindow::doActionCreatePlane()
- {
- ccPlaneEditDlg* peDlg = new ccPlaneEditDlg(m_pickingHub, this);
- peDlg->show();
- }
- void MainWindow::doActionEditPlane()
- {
- if (!haveSelection())
- {
- assert(false);
- return;
- }
- ccPlane* plane = ccHObjectCaster::ToPlane(m_selectedEntities.front());
- if (!plane)
- {
- assert(false);
- return;
- }
- ccPlaneEditDlg* peDlg = new ccPlaneEditDlg(m_pickingHub, this);
- peDlg->initWithPlane(plane);
- peDlg->show();
- }
- void MainWindow::doActionFlipPlane()
- {
- if (!haveSelection())
- {
- assert(false);
- return;
- }
- for (ccHObject* entity : m_selectedEntities)
- {
- ccPlane* plane = ccHObjectCaster::ToPlane(entity);
- if (plane)
- {
- plane->flip();
- plane->prepareDisplayForRefresh();
- }
- }
- refreshAll();
- updatePropertiesView();
- }
- void MainWindow::doActionComparePlanes()
- {
- if (m_selectedEntities.size() != 2)
- {
- ccConsole::Error(tr("Select 2 planes!"));
- return;
- }
- if (!m_selectedEntities.front()->isKindOf(CC_TYPES::PLANE) ||
- !m_selectedEntities.back()->isKindOf(CC_TYPES::PLANE))
- {
- ccConsole::Error(tr("Select 2 planes!"));
- return;
- }
- ccPlane* p1 = ccHObjectCaster::ToPlane(m_selectedEntities.front());
- ccPlane* p2 = ccHObjectCaster::ToPlane(m_selectedEntities.back());
- QStringList info;
- info << tr("Plane 1: %1").arg(p1->getName());
- ccLog::Print(tr("[Compare] ") + info.last());
- info << tr("Plane 2: %1").arg(p2->getName());
- ccLog::Print(tr("[Compare] ") + info.last());
- CCVector3 N1;
- CCVector3 N2;
- PointCoordinateType d1;
- PointCoordinateType d2;
- p1->getEquation(N1, d1);
- p2->getEquation(N2, d2);
- double angle_rad = N1.angle_rad(N2);
- info << tr("Angle P1/P2: %1 deg.").arg( CCCoreLib::RadiansToDegrees( angle_rad ) );
- ccLog::Print(tr("[Compare] ") + info.last());
- PointCoordinateType planeEq1[4] = { N1.x, N1.y, N1.z, d1 };
- PointCoordinateType planeEq2[4] = { N2.x, N2.y, N2.z, d2 };
- CCVector3 C1 = p1->getCenter();
- ScalarType distCenter1ToPlane2 = CCCoreLib::DistanceComputationTools::computePoint2PlaneDistance(&C1, planeEq2);
- info << tr("Distance Center(P1)/P2: %1").arg(distCenter1ToPlane2);
- ccLog::Print(tr("[Compare] ") + info.last());
- CCVector3 C2 = p2->getCenter();
- ScalarType distCenter2ToPlane1 = CCCoreLib::DistanceComputationTools::computePoint2PlaneDistance(&C2, planeEq1);
- info << tr("Distance Center(P2)/P1: %1").arg(distCenter2ToPlane1);
- ccLog::Print(tr("[Compare] ") + info.last());
- //pop-up summary
- QMessageBox::information(this, tr("Plane comparison"), info.join("\n"));
- forceConsoleDisplay();
- }
|