| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873 |
- # ###########################################################
- # FlatCAM: 2D Post-processing for Manufacturing #
- # http://flatcam.org #
- # Author: Juan Pablo Caram (c) #
- # Date: 2/5/2014 #
- # MIT Licence #
- # ###########################################################
- import urllib.request
- import urllib.parse
- import urllib.error
- import getopt
- import random
- import simplejson as json
- import lzma
- import threading
- import shutil
- from stat import S_IREAD, S_IRGRP, S_IROTH
- import subprocess
- import ctypes
- import tkinter as tk
- from PyQt5 import QtPrintSupport
- from contextlib import contextmanager
- import gc
- from xml.dom.minidom import parseString as parse_xml_string
- from multiprocessing.connection import Listener, Client
- from multiprocessing import Pool, cpu_count
- import socket
- from array import array
- # #######################################
- # # Imports part of FlatCAM ##
- # #######################################
- from ObjectCollection import *
- from FlatCAMObj import *
- from flatcamGUI.PlotCanvas import *
- from flatcamGUI.PlotCanvasLegacy import *
- from flatcamGUI.FlatCAMGUI import *
- from FlatCAMCommon import LoudDict
- from FlatCAMPostProc import load_postprocessors
- from flatcamEditors.FlatCAMGeoEditor import FlatCAMGeoEditor
- from flatcamEditors.FlatCAMExcEditor import FlatCAMExcEditor
- from flatcamEditors.FlatCAMGrbEditor import FlatCAMGrbEditor
- from flatcamEditors.FlatCAMTextEditor import TextEditor
- from FlatCAMProcess import *
- from FlatCAMWorkerStack import WorkerStack
- from flatcamGUI.VisPyVisuals import Color
- from vispy.gloo.util import _screenshot
- from vispy.io import write_png
- from flatcamTools import *
- import tclCommands
- import gettext
- import FlatCAMTranslation as fcTranslate
- import builtins
- if sys.platform == 'win32':
- import winreg
- fcTranslate.apply_language('strings')
- if '_' not in builtins.__dict__:
- _ = gettext.gettext
- # ########################################
- # # App ###
- # ########################################
- class App(QtCore.QObject):
- """
- The main application class. The constructor starts the GUI.
- """
- # Get Cmd Line Options
- cmd_line_shellfile = ''
- cmd_line_shellvar = ''
- cmd_line_headless = None
- cmd_line_help = "FlatCam.py --shellfile=<cmd_line_shellfile>\n" \
- "FlatCam.py --shellvar=<1,'C:\\path',23>\n" \
- "FlatCam.py --headless=1"
- try:
- # Multiprocessing pool will spawn additional processes with 'multiprocessing-fork' flag
- cmd_line_options, args = getopt.getopt(sys.argv[1:], "h:", ["shellfile=",
- "shellvar=",
- "headless=",
- "multiprocessing-fork="])
- except getopt.GetoptError:
- print(cmd_line_help)
- sys.exit(2)
- for opt, arg in cmd_line_options:
- if opt == '-h':
- print(cmd_line_help)
- sys.exit()
- elif opt == '--shellfile':
- cmd_line_shellfile = arg
- elif opt == '--shellvar':
- cmd_line_shellvar = arg
- elif opt == '--headless':
- try:
- cmd_line_headless = eval(arg)
- except NameError:
- pass
- # ## Logging ###
- log = logging.getLogger('base')
- log.setLevel(logging.DEBUG)
- # log.setLevel(logging.WARNING)
- formatter = logging.Formatter('[%(levelname)s][%(threadName)s] %(message)s')
- handler = logging.StreamHandler()
- handler.setFormatter(formatter)
- log.addHandler(handler)
- # ##########################################################################
- # ################## Version and VERSION DATE ##############################
- # ##########################################################################
- version = 8.98
- version_date = "2019/10/7"
- beta = True
- engine = '3D'
- # current date now
- date = str(datetime.today()).rpartition('.')[0]
- date = ''.join(c for c in date if c not in ':-')
- date = date.replace(' ', '_')
- # URL for update checks and statistics
- version_url = "http://flatcam.org/version"
- # App URL
- app_url = "http://flatcam.org"
- # Manual URL
- manual_url = "http://flatcam.org/manual/index.html"
- video_url = "https://www.youtube.com/playlist?list=PLVvP2SYRpx-AQgNlfoxw93tXUXon7G94_"
- gerber_spec_url ="https://www.ucamco.com/files/downloads/file/81/The_Gerber_File_Format_specification." \
- "pdf?7ac957791daba2cdf4c2c913f67a43da"
- excellon_spec_url = "https://www.ucamco.com/files/downloads/file/305/the_xnc_file_format_specification.pdf"
- bug_report_url = "https://bitbucket.org/jpcgt/flatcam/issues?status=new&status=open"
- # this variable will hold the project status
- # if True it will mean that the project was modified and not saved
- should_we_save = False
- # flag is True if saving action has been triggered
- save_in_progress = False
- # ###########################################################################
- # ############################# Signals ################################
- # ###########################################################################
- # Inform the user
- # Handled by:
- # * App.info() --> Print on the status bar
- inform = QtCore.pyqtSignal(str)
- app_quit = QtCore.pyqtSignal()
- # General purpose background task
- worker_task = QtCore.pyqtSignal(dict)
- # File opened
- # Handled by:
- # * register_folder()
- # * register_recent()
- # Note: Setting the parameters to unicode does not seem
- # to have an effect. Then are received as Qstring
- # anyway.
- # File type and filename
- file_opened = QtCore.pyqtSignal(str, str)
- # File type and filename
- file_saved = QtCore.pyqtSignal(str, str)
- # Percentage of progress
- progress = QtCore.pyqtSignal(int)
- plots_updated = QtCore.pyqtSignal()
- # Emitted by new_object() and passes the new object as argument, plot flag.
- # on_object_created() adds the object to the collection, plots on appropriate flag
- # and emits new_object_available.
- object_created = QtCore.pyqtSignal(object, bool, bool)
- # Emitted when a object has been changed (like scaled, mirrored)
- object_changed = QtCore.pyqtSignal(object)
- # Emitted after object has been plotted.
- # Calls 'on_zoom_fit' method to fit object in scene view in main thread to prevent drawing glitches.
- object_plotted = QtCore.pyqtSignal(object)
- # Emitted when a new object has been added or deleted from/to the collection
- object_status_changed = QtCore.pyqtSignal(object, str, str)
- message = QtCore.pyqtSignal(str, str, str)
- # Emmited when shell command is finished(one command only)
- shell_command_finished = QtCore.pyqtSignal(object)
- # Emitted when multiprocess pool has been recreated
- pool_recreated = QtCore.pyqtSignal(object)
- # Emitted when an unhandled exception happens
- # in the worker task.
- thread_exception = QtCore.pyqtSignal(object)
- # used to signal that there are arguments for the app
- args_at_startup = QtCore.pyqtSignal(list)
- # a reusable signal to replot a list of objects
- # should be disconnected after use so it can be reused
- replot_signal = pyqtSignal(list)
- def __init__(self, user_defaults=True, post_gui=None):
- """
- Starts the application.
- :return: app
- :rtype: App
- """
- App.log.info("FlatCAM Starting...")
- self.main_thread = QtWidgets.QApplication.instance().thread()
- # ############################################################################
- # # ################# OS-specific ############################################
- # ############################################################################
- portable = False
- # Folder for user settings.
- if sys.platform == 'win32':
- # #########################################################################
- # Setup the listening thread for another instance launching with args #####
- # #########################################################################
- # make sure the thread is stored by using a self. otherwise it's garbage collected
- self.th = QtCore.QThread()
- self.th.start(priority=QtCore.QThread.LowestPriority)
- self.new_launch = ArgsThread()
- self.new_launch.open_signal[list].connect(self.on_startup_args)
- self.new_launch.moveToThread(self.th)
- self.new_launch.start.emit()
- from win32com.shell import shell, shellcon
- if platform.architecture()[0] == '32bit':
- App.log.debug("Win32!")
- else:
- App.log.debug("Win64!")
- # #########################################################################
- # ####### CONFIG FILE WITH PARAMETERS REGARDING PORTABILITY ###############
- # #########################################################################
- config_file = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '\\config\\configuration.txt'
- try:
- with open(config_file, 'r'):
- pass
- except FileNotFoundError:
- config_file = os.path.dirname(os.path.realpath(__file__)) + '\\config\\configuration.txt'
- try:
- with open(config_file, 'r') as f:
- try:
- for line in f:
- param = str(line).replace('\n', '').rpartition('=')
- if param[0] == 'portable':
- try:
- portable = eval(param[2])
- except NameError:
- portable = False
- if param[0] == 'headless':
- if param[2].lower() == 'true':
- self.cmd_line_headless = 1
- else:
- self.cmd_line_headless = None
- except Exception as e:
- log.debug('App.__init__() -->%s' % str(e))
- return
- except FileNotFoundError as e:
- log.debug(str(e))
- pass
- if portable is False:
- self.data_path = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, None, 0) + '\\FlatCAM'
- else:
- self.data_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '\\config'
- self.os = 'windows'
- else: # Linux/Unix/MacOS
- self.data_path = os.path.expanduser('~') + '/.FlatCAM'
- self.os = 'unix'
- # ##########################################################################
- # ################## Setup folders and files ###############################
- # ##########################################################################
- if not os.path.exists(self.data_path):
- os.makedirs(self.data_path)
- App.log.debug('Created data folder: ' + self.data_path)
- os.makedirs(os.path.join(self.data_path, 'postprocessors'))
- App.log.debug('Created data postprocessors folder: ' + os.path.join(self.data_path, 'postprocessors'))
- self.postprocessorpaths = os.path.join(self.data_path, 'postprocessors')
- if not os.path.exists(self.postprocessorpaths):
- os.makedirs(self.postprocessorpaths)
- App.log.debug('Created postprocessors folder: ' + self.postprocessorpaths)
- # create current_defaults.FlatConfig file if there is none
- try:
- f = open(self.data_path + '/current_defaults.FlatConfig')
- f.close()
- except IOError:
- App.log.debug('Creating empty current_defaults.FlatConfig')
- f = open(self.data_path + '/current_defaults.FlatConfig', 'w')
- json.dump({}, f)
- f.close()
- # create factory_defaults.FlatConfig file if there is none
- try:
- f = open(self.data_path + '/factory_defaults.FlatConfig')
- f.close()
- except IOError:
- App.log.debug('Creating empty factory_defaults.FlatConfig')
- f = open(self.data_path + '/factory_defaults.FlatConfig', 'w')
- json.dump({}, f)
- f.close()
- # create a recent files json file if there is none
- try:
- f = open(self.data_path + '/recent.json')
- f.close()
- except IOError:
- App.log.debug('Creating empty recent.json')
- f = open(self.data_path + '/recent.json', 'w')
- json.dump([], f)
- f.close()
- # create a recent projects json file if there is none
- try:
- fp = open(self.data_path + '/recent_projects.json')
- fp.close()
- except IOError:
- App.log.debug('Creating empty recent_projects.json')
- fp = open(self.data_path + '/recent_projects.json', 'w')
- json.dump([], fp)
- fp.close()
- # Application directory. CHDIR to it. Otherwise, trying to load
- # GUI icons will fail as their path is relative.
- # This will fail under cx_freeze ...
- self.app_home = os.path.dirname(os.path.realpath(__file__))
- App.log.debug("Application path is " + self.app_home)
- App.log.debug("Started in " + os.getcwd())
- # cx_freeze workaround
- if os.path.isfile(self.app_home):
- self.app_home = os.path.dirname(self.app_home)
- os.chdir(self.app_home)
- # #############################################################################
- # ##################### CREATE MULTIPROCESSING POOL ###########################
- # #############################################################################
- self.pool = Pool(processes=cpu_count())
- # ##########################################################################
- # ################## Setting the Splash Screen #############################
- # ##########################################################################
- settings = QSettings("Open Source", "FlatCAM")
- if settings.contains("splash_screen"):
- show_splash = settings.value("splash_screen")
- else:
- settings.setValue('splash_screen', 1)
- # This will write the setting to the platform specific storage.
- del settings
- show_splash = 1
- if show_splash and self.cmd_line_headless != 1:
- splash_pix = QtGui.QPixmap('share/splash.png')
- self.splash = QtWidgets.QSplashScreen(splash_pix, Qt.WindowStaysOnTopHint)
- # self.splash.setMask(splash_pix.mask())
- # move splashscreen to the current monitor
- desktop = QtWidgets.QApplication.desktop()
- screen = desktop.screenNumber(QtGui.QCursor.pos())
- current_screen_center = desktop.availableGeometry(screen).center()
- self.splash.move(current_screen_center - self.splash.rect().center())
- self.splash.show()
- self.splash.showMessage(_("FlatCAM is initializing ..."),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- else:
- show_splash = 0
- # #############################################################################
- # ##################### Initialize GUI ########################################
- # #############################################################################
- # FlatCAM colors used in plotting
- self.FC_light_green = '#BBF268BF'
- self.FC_dark_green = '#006E20BF'
- self.FC_light_blue = '#a5a5ffbf'
- self.FC_dark_blue = '#0000ffbf'
- QtCore.QObject.__init__(self)
- self.ui = FlatCAMGUI(self.version, self.beta, self)
- self.ui.geom_update[int, int, int, int, int].connect(self.save_geometry)
- self.ui.final_save.connect(self.final_save)
- # #############################################################################
- # ############################## Data #########################################
- # #############################################################################
- self.recent = []
- self.recent_projects = []
- self.clipboard = QtWidgets.QApplication.clipboard()
- self.project_filename = None
- self.toggle_units_ignore = False
- # self.defaults_form = PreferencesUI()
- # when adding entries here read the comments in the method found bellow named:
- # def new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
- self.defaults_form_fields = {
- # General App
- "units": self.ui.general_defaults_form.general_app_group.units_radio,
- "global_graphic_engine": self.ui.general_defaults_form.general_app_group.ge_radio,
- "global_app_level": self.ui.general_defaults_form.general_app_group.app_level_radio,
- "global_portable": self.ui.general_defaults_form.general_app_group.portability_cb,
- "global_language": self.ui.general_defaults_form.general_app_group.language_cb,
- "global_version_check": self.ui.general_defaults_form.general_app_group.version_check_cb,
- "global_send_stats": self.ui.general_defaults_form.general_app_group.send_stats_cb,
- "global_pan_button": self.ui.general_defaults_form.general_app_group.pan_button_radio,
- "global_mselect_key": self.ui.general_defaults_form.general_app_group.mselect_radio,
- "global_worker_number": self.ui.general_defaults_form.general_app_group.worker_number_sb,
- "global_tolerance": self.ui.general_defaults_form.general_app_group.tol_entry,
- "global_open_style": self.ui.general_defaults_form.general_app_group.open_style_cb,
- "global_compression_level": self.ui.general_defaults_form.general_app_group.compress_combo,
- "global_save_compressed": self.ui.general_defaults_form.general_app_group.save_type_cb,
- # General GUI Preferences
- "global_gridx": self.ui.general_defaults_form.general_gui_group.gridx_entry,
- "global_gridy": self.ui.general_defaults_form.general_gui_group.gridy_entry,
- "global_snap_max": self.ui.general_defaults_form.general_gui_group.snap_max_dist_entry,
- "global_workspace": self.ui.general_defaults_form.general_gui_group.workspace_cb,
- "global_workspaceT": self.ui.general_defaults_form.general_gui_group.wk_cb,
- "global_plot_fill": self.ui.general_defaults_form.general_gui_group.pf_color_entry,
- "global_plot_line": self.ui.general_defaults_form.general_gui_group.pl_color_entry,
- "global_sel_fill": self.ui.general_defaults_form.general_gui_group.sf_color_entry,
- "global_sel_line": self.ui.general_defaults_form.general_gui_group.sl_color_entry,
- "global_alt_sel_fill": self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry,
- "global_alt_sel_line": self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry,
- "global_draw_color": self.ui.general_defaults_form.general_gui_group.draw_color_entry,
- "global_sel_draw_color": self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry,
- "global_proj_item_color": self.ui.general_defaults_form.general_gui_group.proj_color_entry,
- "global_proj_item_dis_color": self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry,
- "global_activity_icon": self.ui.general_defaults_form.general_gui_group.activity_combo,
- # General GUI Settings
- "global_layout": self.ui.general_defaults_form.general_gui_set_group.layout_combo,
- "global_hover": self.ui.general_defaults_form.general_gui_set_group.hover_cb,
- "global_selection_shape": self.ui.general_defaults_form.general_gui_set_group.selection_cb,
- "global_systray_icon": self.ui.general_defaults_form.general_gui_set_group.systray_cb,
- "global_shell_at_startup": self.ui.general_defaults_form.general_gui_set_group.shell_startup_cb,
- "global_project_at_startup": self.ui.general_defaults_form.general_gui_set_group.project_startup_cb,
- "global_project_autohide": self.ui.general_defaults_form.general_gui_set_group.project_autohide_cb,
- "global_toggle_tooltips": self.ui.general_defaults_form.general_gui_set_group.toggle_tooltips_cb,
- "global_delete_confirmation": self.ui.general_defaults_form.general_gui_set_group.delete_conf_cb,
- "global_cursor_type": self.ui.general_defaults_form.general_gui_set_group.cursor_radio,
- "global_cursor_size": self.ui.general_defaults_form.general_gui_set_group.cursor_size_entry,
- # Gerber General
- "gerber_plot": self.ui.gerber_defaults_form.gerber_gen_group.plot_cb,
- "gerber_solid": self.ui.gerber_defaults_form.gerber_gen_group.solid_cb,
- "gerber_multicolored": self.ui.gerber_defaults_form.gerber_gen_group.multicolored_cb,
- "gerber_circle_steps": self.ui.gerber_defaults_form.gerber_gen_group.circle_steps_entry,
- "gerber_def_units": self.ui.gerber_defaults_form.gerber_gen_group.gerber_units_radio,
- "gerber_def_zeros": self.ui.gerber_defaults_form.gerber_gen_group.gerber_zeros_radio,
- # Gerber Options
- "gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry,
- "gerber_isopasses": self.ui.gerber_defaults_form.gerber_opt_group.iso_width_entry,
- "gerber_isooverlap": self.ui.gerber_defaults_form.gerber_opt_group.iso_overlap_entry,
- "gerber_combine_passes": self.ui.gerber_defaults_form.gerber_opt_group.combine_passes_cb,
- "gerber_milling_type": self.ui.gerber_defaults_form.gerber_opt_group.milling_type_radio,
- "gerber_noncoppermargin": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_margin_entry,
- "gerber_noncopperrounded": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_rounded_cb,
- "gerber_bboxmargin": self.ui.gerber_defaults_form.gerber_opt_group.bbmargin_entry,
- "gerber_bboxrounded": self.ui.gerber_defaults_form.gerber_opt_group.bbrounded_cb,
- # Gerber Advanced Options
- "gerber_aperture_display": self.ui.gerber_defaults_form.gerber_adv_opt_group.aperture_table_visibility_cb,
- # "gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry,
- # "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry,
- "gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb,
- "gerber_tool_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.tool_type_radio,
- "gerber_vtipdia": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipdia_spinner,
- "gerber_vtipangle": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipangle_spinner,
- "gerber_vcutz": self.ui.gerber_defaults_form.gerber_adv_opt_group.cutz_spinner,
- "gerber_buffering": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffering_radio,
- "gerber_simplification": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplify_cb,
- "gerber_simp_tolerance": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplification_tol_spinner,
- # Gerber Export
- "gerber_exp_units": self.ui.gerber_defaults_form.gerber_exp_group.gerber_units_radio,
- "gerber_exp_integer": self.ui.gerber_defaults_form.gerber_exp_group.format_whole_entry,
- "gerber_exp_decimals": self.ui.gerber_defaults_form.gerber_exp_group.format_dec_entry,
- "gerber_exp_zeros": self.ui.gerber_defaults_form.gerber_exp_group.zeros_radio,
- # Gerber Editor
- "gerber_editor_sel_limit": self.ui.gerber_defaults_form.gerber_editor_group.sel_limit_entry,
- "gerber_editor_newcode": self.ui.gerber_defaults_form.gerber_editor_group.addcode_entry,
- "gerber_editor_newsize": self.ui.gerber_defaults_form.gerber_editor_group.addsize_entry,
- "gerber_editor_newtype": self.ui.gerber_defaults_form.gerber_editor_group.addtype_combo,
- "gerber_editor_newdim": self.ui.gerber_defaults_form.gerber_editor_group.adddim_entry,
- "gerber_editor_array_size": self.ui.gerber_defaults_form.gerber_editor_group.grb_array_size_entry,
- "gerber_editor_lin_axis": self.ui.gerber_defaults_form.gerber_editor_group.grb_axis_radio,
- "gerber_editor_lin_pitch": self.ui.gerber_defaults_form.gerber_editor_group.grb_pitch_entry,
- "gerber_editor_lin_angle": self.ui.gerber_defaults_form.gerber_editor_group.grb_angle_entry,
- "gerber_editor_circ_dir": self.ui.gerber_defaults_form.gerber_editor_group.grb_circular_dir_radio,
- "gerber_editor_circ_angle":
- self.ui.gerber_defaults_form.gerber_editor_group.grb_circular_angle_entry,
- "gerber_editor_scale_f": self.ui.gerber_defaults_form.gerber_editor_group.grb_scale_entry,
- "gerber_editor_buff_f": self.ui.gerber_defaults_form.gerber_editor_group.grb_buff_entry,
- "gerber_editor_ma_low": self.ui.gerber_defaults_form.gerber_editor_group.grb_ma_low_entry,
- "gerber_editor_ma_high": self.ui.gerber_defaults_form.gerber_editor_group.grb_ma_high_entry,
- # Excellon General
- "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
- "excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb,
- "excellon_format_upper_in":
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry,
- "excellon_format_lower_in":
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_in_entry,
- "excellon_format_upper_mm":
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_mm_entry,
- "excellon_format_lower_mm":
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_mm_entry,
- "excellon_zeros": self.ui.excellon_defaults_form.excellon_gen_group.excellon_zeros_radio,
- "excellon_units": self.ui.excellon_defaults_form.excellon_gen_group.excellon_units_radio,
- "excellon_update": self.ui.excellon_defaults_form.excellon_gen_group.update_excellon_cb,
- "excellon_optimization_type": self.ui.excellon_defaults_form.excellon_gen_group.excellon_optimization_radio,
- "excellon_search_time": self.ui.excellon_defaults_form.excellon_gen_group.optimization_time_entry,
- # Excellon Options
- "excellon_drillz": self.ui.excellon_defaults_form.excellon_opt_group.cutz_entry,
- "excellon_travelz": self.ui.excellon_defaults_form.excellon_opt_group.travelz_entry,
- "excellon_endz": self.ui.excellon_defaults_form.excellon_opt_group.eendz_entry,
- "excellon_feedrate": self.ui.excellon_defaults_form.excellon_opt_group.feedrate_entry,
- "excellon_spindlespeed": self.ui.excellon_defaults_form.excellon_opt_group.spindlespeed_entry,
- "excellon_dwell": self.ui.excellon_defaults_form.excellon_opt_group.dwell_cb,
- "excellon_dwelltime": self.ui.excellon_defaults_form.excellon_opt_group.dwelltime_entry,
- "excellon_toolchange": self.ui.excellon_defaults_form.excellon_opt_group.toolchange_cb,
- "excellon_toolchangez": self.ui.excellon_defaults_form.excellon_opt_group.toolchangez_entry,
- "excellon_ppname_e": self.ui.excellon_defaults_form.excellon_opt_group.pp_excellon_name_cb,
- "excellon_tooldia": self.ui.excellon_defaults_form.excellon_opt_group.tooldia_entry,
- "excellon_slot_tooldia": self.ui.excellon_defaults_form.excellon_opt_group.slot_tooldia_entry,
- "excellon_gcode_type": self.ui.excellon_defaults_form.excellon_opt_group.excellon_gcode_type_radio,
- # Excellon Advanced Options
- "excellon_offset": self.ui.excellon_defaults_form.excellon_adv_opt_group.offset_entry,
- "excellon_toolchangexy": self.ui.excellon_defaults_form.excellon_adv_opt_group.toolchangexy_entry,
- "excellon_startz": self.ui.excellon_defaults_form.excellon_adv_opt_group.estartz_entry,
- "excellon_feedrate_rapid": self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_rapid_entry,
- "excellon_z_pdepth": self.ui.excellon_defaults_form.excellon_adv_opt_group.pdepth_entry,
- "excellon_feedrate_probe": self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_probe_entry,
- "excellon_spindledir": self.ui.excellon_defaults_form.excellon_adv_opt_group.spindledir_radio,
- "excellon_f_plunge": self.ui.excellon_defaults_form.excellon_adv_opt_group.fplunge_cb,
- "excellon_f_retract": self.ui.excellon_defaults_form.excellon_adv_opt_group.fretract_cb,
- # Excellon Export
- "excellon_exp_units": self.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio,
- "excellon_exp_format": self.ui.excellon_defaults_form.excellon_exp_group.format_radio,
- "excellon_exp_integer": self.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry,
- "excellon_exp_decimals": self.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry,
- "excellon_exp_zeros": self.ui.excellon_defaults_form.excellon_exp_group.zeros_radio,
- "excellon_exp_slot_type": self.ui.excellon_defaults_form.excellon_exp_group.slot_type_radio,
- # Excellon Editor
- "excellon_editor_sel_limit": self.ui.excellon_defaults_form.excellon_editor_group.sel_limit_entry,
- "excellon_editor_newdia": self.ui.excellon_defaults_form.excellon_editor_group.addtool_entry,
- "excellon_editor_array_size": self.ui.excellon_defaults_form.excellon_editor_group.drill_array_size_entry,
- "excellon_editor_lin_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_axis_radio,
- "excellon_editor_lin_pitch": self.ui.excellon_defaults_form.excellon_editor_group.drill_pitch_entry,
- "excellon_editor_lin_angle": self.ui.excellon_defaults_form.excellon_editor_group.drill_angle_entry,
- "excellon_editor_circ_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_dir_radio,
- "excellon_editor_circ_angle":
- self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_angle_entry,
- # Excellon Slots
- "excellon_editor_slot_direction":
- self.ui.excellon_defaults_form.excellon_editor_group.slot_axis_radio,
- "excellon_editor_slot_angle":
- self.ui.excellon_defaults_form.excellon_editor_group.slot_angle_spinner,
- "excellon_editor_slot_length":
- self.ui.excellon_defaults_form.excellon_editor_group.slot_length_entry,
- # Excellon Slots
- "excellon_editor_slot_array_size":
- self.ui.excellon_defaults_form.excellon_editor_group.slot_array_size_entry,
- "excellon_editor_slot_lin_dir": self.ui.excellon_defaults_form.excellon_editor_group.slot_array_axis_radio,
- "excellon_editor_slot_lin_pitch":
- self.ui.excellon_defaults_form.excellon_editor_group.slot_array_pitch_entry,
- "excellon_editor_slot_lin_angle":
- self.ui.excellon_defaults_form.excellon_editor_group.slot_array_angle_entry,
- "excellon_editor_slot_circ_dir":
- self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_dir_radio,
- "excellon_editor_slot_circ_angle":
- self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_angle_entry,
- # Geometry General
- "geometry_plot": self.ui.geometry_defaults_form.geometry_gen_group.plot_cb,
- "geometry_circle_steps": self.ui.geometry_defaults_form.geometry_gen_group.circle_steps_entry,
- "geometry_cnctooldia": self.ui.geometry_defaults_form.geometry_gen_group.cnctooldia_entry,
- # Geometry Options
- "geometry_cutz": self.ui.geometry_defaults_form.geometry_opt_group.cutz_entry,
- "geometry_travelz": self.ui.geometry_defaults_form.geometry_opt_group.travelz_entry,
- "geometry_feedrate": self.ui.geometry_defaults_form.geometry_opt_group.cncfeedrate_entry,
- "geometry_feedrate_z": self.ui.geometry_defaults_form.geometry_opt_group.cncplunge_entry,
- "geometry_spindlespeed": self.ui.geometry_defaults_form.geometry_opt_group.cncspindlespeed_entry,
- "geometry_dwell": self.ui.geometry_defaults_form.geometry_opt_group.dwell_cb,
- "geometry_dwelltime": self.ui.geometry_defaults_form.geometry_opt_group.dwelltime_entry,
- "geometry_ppname_g": self.ui.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb,
- "geometry_toolchange": self.ui.geometry_defaults_form.geometry_opt_group.toolchange_cb,
- "geometry_toolchangez": self.ui.geometry_defaults_form.geometry_opt_group.toolchangez_entry,
- "geometry_endz": self.ui.geometry_defaults_form.geometry_opt_group.gendz_entry,
- "geometry_depthperpass": self.ui.geometry_defaults_form.geometry_opt_group.depthperpass_entry,
- "geometry_multidepth": self.ui.geometry_defaults_form.geometry_opt_group.multidepth_cb,
- # Geometry Advanced Options
- "geometry_toolchangexy": self.ui.geometry_defaults_form.geometry_adv_opt_group.toolchangexy_entry,
- "geometry_startz": self.ui.geometry_defaults_form.geometry_adv_opt_group.gstartz_entry,
- "geometry_feedrate_rapid": self.ui.geometry_defaults_form.geometry_adv_opt_group.cncfeedrate_rapid_entry,
- "geometry_extracut": self.ui.geometry_defaults_form.geometry_adv_opt_group.extracut_cb,
- "geometry_z_pdepth": self.ui.geometry_defaults_form.geometry_adv_opt_group.pdepth_entry,
- "geometry_feedrate_probe": self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_probe_entry,
- "geometry_spindledir": self.ui.geometry_defaults_form.geometry_adv_opt_group.spindledir_radio,
- "geometry_f_plunge": self.ui.geometry_defaults_form.geometry_adv_opt_group.fplunge_cb,
- "geometry_segx": self.ui.geometry_defaults_form.geometry_adv_opt_group.segx_entry,
- "geometry_segy": self.ui.geometry_defaults_form.geometry_adv_opt_group.segy_entry,
- # Geometry Editor
- "geometry_editor_sel_limit": self.ui.geometry_defaults_form.geometry_editor_group.sel_limit_entry,
- "geometry_editor_milling_type": self.ui.geometry_defaults_form.geometry_editor_group.milling_type_radio,
- # CNCJob General
- "cncjob_plot": self.ui.cncjob_defaults_form.cncjob_gen_group.plot_cb,
- "cncjob_plot_kind": self.ui.cncjob_defaults_form.cncjob_gen_group.cncplot_method_radio,
- "cncjob_annotation": self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_cb,
- "cncjob_tooldia": self.ui.cncjob_defaults_form.cncjob_gen_group.tooldia_entry,
- "cncjob_coords_type": self.ui.cncjob_defaults_form.cncjob_gen_group.coords_type_radio,
- "cncjob_coords_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.coords_dec_entry,
- "cncjob_fr_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.fr_dec_entry,
- "cncjob_steps_per_circle": self.ui.cncjob_defaults_form.cncjob_gen_group.steps_per_circle_entry,
- # CNC Job Options
- "cncjob_prepend": self.ui.cncjob_defaults_form.cncjob_opt_group.prepend_text,
- "cncjob_append": self.ui.cncjob_defaults_form.cncjob_opt_group.append_text,
- # CNC Job Advanced Options
- "cncjob_toolchange_macro": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_text,
- "cncjob_toolchange_macro_enable": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_cb,
- "cncjob_annotation_fontsize": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontsize_sp,
- "cncjob_annotation_fontcolor": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry,
- # NCC Tool
- "tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
- "tools_nccorder": self.ui.tools_defaults_form.tools_ncc_group.ncc_order_radio,
- "tools_nccoverlap": self.ui.tools_defaults_form.tools_ncc_group.ncc_overlap_entry,
- "tools_nccmargin": self.ui.tools_defaults_form.tools_ncc_group.ncc_margin_entry,
- "tools_nccmethod": self.ui.tools_defaults_form.tools_ncc_group.ncc_method_radio,
- "tools_nccconnect": self.ui.tools_defaults_form.tools_ncc_group.ncc_connect_cb,
- "tools_ncccontour": self.ui.tools_defaults_form.tools_ncc_group.ncc_contour_cb,
- "tools_nccrest": self.ui.tools_defaults_form.tools_ncc_group.ncc_rest_cb,
- "tools_ncc_offset_choice": self.ui.tools_defaults_form.tools_ncc_group.ncc_choice_offset_cb,
- "tools_ncc_offset_value": self.ui.tools_defaults_form.tools_ncc_group.ncc_offset_spinner,
- "tools_nccref": self.ui.tools_defaults_form.tools_ncc_group.reference_radio,
- "tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.ncc_plotting_radio,
- "tools_nccmilling_type": self.ui.tools_defaults_form.tools_ncc_group.milling_type_radio,
- "tools_ncctool_type": self.ui.tools_defaults_form.tools_ncc_group.tool_type_radio,
- "tools_ncccutz": self.ui.tools_defaults_form.tools_ncc_group.cutz_entry,
- "tools_ncctipdia": self.ui.tools_defaults_form.tools_ncc_group.tipdia_entry,
- "tools_ncctipangle": self.ui.tools_defaults_form.tools_ncc_group.tipangle_entry,
- # CutOut Tool
- "tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,
- "tools_cutoutkind": self.ui.tools_defaults_form.tools_cutout_group.obj_kind_combo,
- "tools_cutoutmargin": self.ui.tools_defaults_form.tools_cutout_group.cutout_margin_entry,
- "tools_cutoutgapsize": self.ui.tools_defaults_form.tools_cutout_group.cutout_gap_entry,
- "tools_gaps_ff": self.ui.tools_defaults_form.tools_cutout_group.gaps_combo,
- "tools_cutout_convexshape": self.ui.tools_defaults_form.tools_cutout_group.convex_box,
- # Paint Area Tool
- "tools_painttooldia": self.ui.tools_defaults_form.tools_paint_group.painttooldia_entry,
- "tools_paintorder": self.ui.tools_defaults_form.tools_paint_group.paint_order_radio,
- "tools_paintoverlap": self.ui.tools_defaults_form.tools_paint_group.paintoverlap_entry,
- "tools_paintmargin": self.ui.tools_defaults_form.tools_paint_group.paintmargin_entry,
- "tools_paintmethod": self.ui.tools_defaults_form.tools_paint_group.paintmethod_combo,
- "tools_selectmethod": self.ui.tools_defaults_form.tools_paint_group.selectmethod_combo,
- "tools_pathconnect": self.ui.tools_defaults_form.tools_paint_group.pathconnect_cb,
- "tools_paintcontour": self.ui.tools_defaults_form.tools_paint_group.contour_cb,
- "tools_paint_plotting": self.ui.tools_defaults_form.tools_paint_group.paint_plotting_radio,
- # 2-sided Tool
- "tools_2sided_mirror_axis": self.ui.tools_defaults_form.tools_2sided_group.mirror_axis_radio,
- "tools_2sided_axis_loc": self.ui.tools_defaults_form.tools_2sided_group.axis_location_radio,
- "tools_2sided_drilldia": self.ui.tools_defaults_form.tools_2sided_group.drill_dia_entry,
- # Film Tool
- "tools_film_type": self.ui.tools_defaults_form.tools_film_group.film_type_radio,
- "tools_film_boundary": self.ui.tools_defaults_form.tools_film_group.film_boundary_entry,
- "tools_film_scale": self.ui.tools_defaults_form.tools_film_group.film_scale_entry,
- "tools_film_color": self.ui.tools_defaults_form.tools_film_group.film_color_entry,
- # Panelize Tool
- "tools_panelize_spacing_columns": self.ui.tools_defaults_form.tools_panelize_group.pspacing_columns,
- "tools_panelize_spacing_rows": self.ui.tools_defaults_form.tools_panelize_group.pspacing_rows,
- "tools_panelize_columns": self.ui.tools_defaults_form.tools_panelize_group.pcolumns,
- "tools_panelize_rows": self.ui.tools_defaults_form.tools_panelize_group.prows,
- "tools_panelize_constrain": self.ui.tools_defaults_form.tools_panelize_group.pconstrain_cb,
- "tools_panelize_constrainx": self.ui.tools_defaults_form.tools_panelize_group.px_width_entry,
- "tools_panelize_constrainy": self.ui.tools_defaults_form.tools_panelize_group.py_height_entry,
- "tools_panelize_panel_type": self.ui.tools_defaults_form.tools_panelize_group.panel_type_radio,
- # Calculators Tool
- "tools_calc_vshape_tip_dia": self.ui.tools_defaults_form.tools_calculators_group.tip_dia_entry,
- "tools_calc_vshape_tip_angle": self.ui.tools_defaults_form.tools_calculators_group.tip_angle_entry,
- "tools_calc_vshape_cut_z": self.ui.tools_defaults_form.tools_calculators_group.cut_z_entry,
- "tools_calc_electro_length": self.ui.tools_defaults_form.tools_calculators_group.pcblength_entry,
- "tools_calc_electro_width": self.ui.tools_defaults_form.tools_calculators_group.pcbwidth_entry,
- "tools_calc_electro_cdensity": self.ui.tools_defaults_form.tools_calculators_group.cdensity_entry,
- "tools_calc_electro_growth": self.ui.tools_defaults_form.tools_calculators_group.growth_entry,
- # Transformations Tool
- "tools_transform_rotate": self.ui.tools_defaults_form.tools_transform_group.rotate_entry,
- "tools_transform_skew_x": self.ui.tools_defaults_form.tools_transform_group.skewx_entry,
- "tools_transform_skew_y": self.ui.tools_defaults_form.tools_transform_group.skewy_entry,
- "tools_transform_scale_x": self.ui.tools_defaults_form.tools_transform_group.scalex_entry,
- "tools_transform_scale_y": self.ui.tools_defaults_form.tools_transform_group.scaley_entry,
- "tools_transform_scale_link": self.ui.tools_defaults_form.tools_transform_group.link_cb,
- "tools_transform_scale_reference": self.ui.tools_defaults_form.tools_transform_group.reference_cb,
- "tools_transform_offset_x": self.ui.tools_defaults_form.tools_transform_group.offx_entry,
- "tools_transform_offset_y": self.ui.tools_defaults_form.tools_transform_group.offy_entry,
- "tools_transform_mirror_reference": self.ui.tools_defaults_form.tools_transform_group.mirror_reference_cb,
- "tools_transform_mirror_point": self.ui.tools_defaults_form.tools_transform_group.flip_ref_entry,
- # SolderPaste Dispensing Tool
- "tools_solderpaste_tools": self.ui.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry,
- "tools_solderpaste_new": self.ui.tools_defaults_form.tools_solderpaste_group.addtool_entry,
- "tools_solderpaste_z_start": self.ui.tools_defaults_form.tools_solderpaste_group.z_start_entry,
- "tools_solderpaste_z_dispense": self.ui.tools_defaults_form.tools_solderpaste_group.z_dispense_entry,
- "tools_solderpaste_z_stop": self.ui.tools_defaults_form.tools_solderpaste_group.z_stop_entry,
- "tools_solderpaste_z_travel": self.ui.tools_defaults_form.tools_solderpaste_group.z_travel_entry,
- "tools_solderpaste_z_toolchange": self.ui.tools_defaults_form.tools_solderpaste_group.z_toolchange_entry,
- "tools_solderpaste_xy_toolchange": self.ui.tools_defaults_form.tools_solderpaste_group.xy_toolchange_entry,
- "tools_solderpaste_frxy": self.ui.tools_defaults_form.tools_solderpaste_group.frxy_entry,
- "tools_solderpaste_frz": self.ui.tools_defaults_form.tools_solderpaste_group.frz_entry,
- "tools_solderpaste_frz_dispense": self.ui.tools_defaults_form.tools_solderpaste_group.frz_dispense_entry,
- "tools_solderpaste_speedfwd": self.ui.tools_defaults_form.tools_solderpaste_group.speedfwd_entry,
- "tools_solderpaste_dwellfwd": self.ui.tools_defaults_form.tools_solderpaste_group.dwellfwd_entry,
- "tools_solderpaste_speedrev": self.ui.tools_defaults_form.tools_solderpaste_group.speedrev_entry,
- "tools_solderpaste_dwellrev": self.ui.tools_defaults_form.tools_solderpaste_group.dwellrev_entry,
- "tools_solderpaste_pp": self.ui.tools_defaults_form.tools_solderpaste_group.pp_combo,
- "tools_sub_close_paths": self.ui.tools_defaults_form.tools_sub_group.close_paths_cb,
- # Utilities
- # File associations
- "fa_excellon": self.ui.util_defaults_form.fa_excellon_group.exc_list_text,
- "fa_gcode": self.ui.util_defaults_form.fa_gcode_group.gco_list_text,
- # "fa_geometry": self.ui.util_defaults_form.fa_geometry_group.close_paths_cb,
- "fa_gerber": self.ui.util_defaults_form.fa_gerber_group.grb_list_text,
- "util_autocomplete_keywords": self.ui.util_defaults_form.kw_group.kw_list_text,
- }
- # ############################
- # ### LOAD POSTPROCESSORS ####
- # ############################
- self.postprocessors = load_postprocessors(self)
- for name in list(self.postprocessors.keys()):
- # 'Paste' postprocessors are to be used only in the Solder Paste Dispensing Tool
- if name.partition('_')[0] == 'Paste':
- self.ui.tools_defaults_form.tools_solderpaste_group.pp_combo.addItem(name)
- continue
- self.ui.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb.addItem(name)
- # HPGL postprocessor is only for Geometry objects therefore it should not be in the Excellon Preferences
- if name == 'hpgl':
- continue
- self.ui.excellon_defaults_form.excellon_opt_group.pp_excellon_name_cb.addItem(name)
- # ############################
- # ### LOAD LANGUAGES ####
- # ############################
- self.languages = fcTranslate.load_languages()
- for name in sorted(self.languages.values()):
- self.ui.general_defaults_form.general_app_group.language_cb.addItem(name)
- # #################################################################################
- # #################### DEFAULTS - PREFERENCES STORAGE #############################
- # #################################################################################
- self.defaults = LoudDict()
- self.defaults.set_change_callback(self.on_defaults_dict_change) # When the dictionary changes.
- self.defaults.update({
- # Global APP Preferences
- "first_run": True,
- "units": "IN",
- "global_serial": 0,
- "global_stats": {},
- "global_tabs_detachable": True,
- "global_graphic_engine": '3D',
- "global_app_level": 'b',
- "global_portable": False,
- "global_language": 'English',
- "global_version_check": True,
- "global_send_stats": True,
- "global_pan_button": '2',
- "global_mselect_key": 'Control',
- "global_project_at_startup": False,
- "global_systray_icon": True,
- "global_project_autohide": True,
- "global_toggle_tooltips": True,
- "global_worker_number": 2,
- "global_tolerance": 0.005,
- "global_open_style": True,
- "global_delete_confirmation": True,
- "global_compression_level": 3,
- "global_save_compressed": True,
- # Global GUI Preferences
- "global_gridx": 0.0393701,
- "global_gridy": 0.0393701,
- "global_snap_max": 0.001968504,
- "global_workspace": False,
- "global_workspaceT": "A4P",
- "global_grid_context_menu": {
- 'in': [0.01, 0.02, 0.025, 0.05, 0.1],
- 'mm': [0.1, 0.2, 0.5, 1, 2.54]
- },
- "global_plot_fill": '#BBF268BF',
- "global_plot_line": '#006E20BF',
- "global_sel_fill": '#a5a5ffbf',
- "global_sel_line": '#0000ffbf',
- "global_alt_sel_fill": '#BBF268BF',
- "global_alt_sel_line": '#006E20BF',
- "global_draw_color": '#FF0000',
- "global_sel_draw_color": '#0000FF',
- "global_proj_item_color": '#000000',
- "global_proj_item_dis_color": '#b7b7cb',
- "global_activity_icon": 'Ball green',
- "global_toolbar_view": 511,
- "global_background_timeout": 300000, # Default value is 5 minutes
- "global_verbose_error_level": 0, # Shell verbosity 0 = default
- # (python trace only for unknown errors),
- # 1 = show trace(show trace always),
- # 2 = (For the future).
- # Persistence
- "global_last_folder": None,
- "global_last_save_folder": None,
- # Default window geometry
- "global_def_win_x": 100,
- "global_def_win_y": 100,
- "global_def_win_w": 1024,
- "global_def_win_h": 650,
- "global_def_notebook_width": 1,
- # Constants...
- "global_defaults_save_period_ms": 20000, # Time between default saves.
- "global_shell_shape": [500, 300], # Shape of the shell in pixels.
- "global_shell_at_startup": False, # Show the shell at startup.
- "global_recent_limit": 10, # Max. items in recent list.
- "fit_key": 'V',
- "zoom_out_key": '-',
- "zoom_in_key": '=',
- "grid_toggle_key": 'G',
- "global_zoom_ratio": 1.5,
- "global_point_clipboard_format": "(%.4f, %.4f)",
- "global_zdownrate": None,
- # General GUI Settings
- "global_hover": False,
- "global_selection_shape": True,
- "global_layout": "compact",
- "global_cursor_type": "small",
- "global_cursor_size": 20,
- # Gerber General
- "gerber_plot": True,
- "gerber_solid": True,
- "gerber_multicolored": False,
- "gerber_circle_steps": 128,
- "gerber_use_buffer_for_union": True,
- "gerber_def_units": 'IN',
- "gerber_def_zeros": 'L',
- # Gerber Options
- "gerber_isotooldia": 0.00787402,
- "gerber_isopasses": 1,
- "gerber_isooverlap": 0.00393701,
- "gerber_milling_type": "cl",
- "gerber_combine_passes": False,
- "gerber_noncoppermargin": 0.00393701,
- "gerber_noncopperrounded": False,
- "gerber_bboxmargin": 0.00393701,
- "gerber_bboxrounded": False,
- # Gerber Advanced Options
- "gerber_aperture_display": False,
- "gerber_aperture_scale_factor": 1.0,
- "gerber_aperture_buffer_factor": 0.0,
- "gerber_follow": False,
- "gerber_tool_type": 'circular',
- "gerber_vtipdia": 0.1,
- "gerber_vtipangle": 30,
- "gerber_vcutz": -0.05,
- "gerber_buffering": "full",
- "gerber_simplification": False,
- "gerber_simp_tolerance": 0.0005,
- # Gerber Export
- "gerber_exp_units": 'IN',
- "gerber_exp_integer": 2,
- "gerber_exp_decimals": 4,
- "gerber_exp_zeros": 'L',
- # Gerber Editor
- "gerber_editor_sel_limit": 30,
- "gerber_editor_newcode": 10,
- "gerber_editor_newsize": 0.8,
- "gerber_editor_newtype": 'C',
- "gerber_editor_newdim": "0.5, 0.5",
- "gerber_editor_array_size": 5,
- "gerber_editor_lin_axis": 'X',
- "gerber_editor_lin_pitch": 1,
- "gerber_editor_lin_angle": 0.0,
- "gerber_editor_circ_dir": 'CW',
- "gerber_editor_circ_angle": 0.0,
- "gerber_editor_scale_f": 1.0,
- "gerber_editor_buff_f": 0.1,
- "gerber_editor_ma_low": 0.0,
- "gerber_editor_ma_high": 1.0,
- # Excellon General
- "excellon_plot": True,
- "excellon_solid": True,
- "excellon_format_upper_in": 2,
- "excellon_format_lower_in": 4,
- "excellon_format_upper_mm": 3,
- "excellon_format_lower_mm": 3,
- "excellon_zeros": "L",
- "excellon_units": "INCH",
- "excellon_update": True,
- "excellon_optimization_type": 'B',
- "excellon_search_time": 3,
- # Excellon Options
- "excellon_drillz": -0.0590551,
- "excellon_travelz": 0.0787402,
- "excellon_endz": 0.5,
- "excellon_feedrate": 3.14961,
- "excellon_spindlespeed": None,
- "excellon_dwell": False,
- "excellon_dwelltime": 1,
- "excellon_toolchange": False,
- "excellon_toolchangez": 0.5,
- "excellon_ppname_e": 'default',
- "excellon_tooldia": 0.0314961,
- "excellon_slot_tooldia": 0.0708661,
- "excellon_gcode_type": "drills",
- # Excellon Advanced Options
- "excellon_offset": 0.0,
- "excellon_toolchangexy": "0.0, 0.0",
- "excellon_startz": None,
- "excellon_feedrate_rapid": 31.4961,
- "excellon_z_pdepth": -0.02,
- "excellon_feedrate_probe": 3.14961,
- "excellon_spindledir": 'CW',
- "excellon_f_plunge": False,
- "excellon_f_retract": False,
- # Excellon Export
- "excellon_exp_units": 'INCH',
- "excellon_exp_format": 'ndec',
- "excellon_exp_integer": 2,
- "excellon_exp_decimals": 4,
- "excellon_exp_zeros": 'LZ',
- "excellon_exp_slot_type": 'routing',
- # Excellon Editor
- "excellon_editor_sel_limit": 30,
- "excellon_editor_newdia": 0.039,
- "excellon_editor_array_size": 5,
- "excellon_editor_lin_dir": 'X',
- "excellon_editor_lin_pitch": 0.1,
- "excellon_editor_lin_angle": 0.0,
- "excellon_editor_circ_dir": 'CW',
- "excellon_editor_circ_angle": 12,
- # Excellon Slots
- "excellon_editor_slot_direction": 'X',
- "excellon_editor_slot_angle": 0.0,
- "excellon_editor_slot_length": 5.0,
- # Excellon Slot Array
- "excellon_editor_slot_array_size": 5,
- "excellon_editor_slot_lin_dir": 'X',
- "excellon_editor_slot_lin_pitch": 0.1,
- "excellon_editor_slot_lin_angle": 0.0,
- "excellon_editor_slot_circ_dir": 'CW',
- "excellon_editor_slot_circ_angle": 0.0,
- # Geometry General
- "geometry_plot": True,
- "geometry_circle_steps": 128,
- "geometry_cnctooldia": "0.0944882",
- # Geometry Options
- "geometry_cutz": -0.0944882,
- "geometry_vtipdia": 0.1,
- "geometry_vtipangle": 30,
- "geometry_multidepth": False,
- "geometry_depthperpass": 0.0314961,
- "geometry_travelz": 0.0787402,
- "geometry_toolchange": False,
- "geometry_toolchangez": 0.5,
- "geometry_endz": 0.5,
- "geometry_feedrate": 3.14961,
- "geometry_feedrate_z": 3.14961,
- "geometry_spindlespeed": None,
- "geometry_dwell": False,
- "geometry_dwelltime": 1,
- "geometry_ppname_g": 'default',
- # Geometry Advanced Options
- "geometry_toolchangexy": "0.0, 0.0",
- "geometry_startz": None,
- "geometry_feedrate_rapid": 3.14961,
- "geometry_extracut": False,
- "geometry_z_pdepth": -0.02,
- "geometry_f_plunge": False,
- "geometry_spindledir": 'CW',
- "geometry_feedrate_probe": 3.14961,
- "geometry_segx": 0.0,
- "geometry_segy": 0.0,
- # Geometry Editor
- "geometry_editor_sel_limit": 30,
- "geometry_editor_milling_type": "cl",
- # CNC Job General
- "cncjob_plot": True,
- "cncjob_plot_kind": 'all',
- "cncjob_annotation": True,
- "cncjob_tooldia": 0.0393701,
- "cncjob_coords_type": "G90",
- "cncjob_coords_decimals": 4,
- "cncjob_fr_decimals": 2,
- "cncjob_steps_per_circle": 128,
- # CNC Job Options
- "cncjob_prepend": "",
- "cncjob_append": "",
- # CNC Job Advanced Options
- "cncjob_toolchange_macro": "",
- "cncjob_toolchange_macro_enable": False,
- "cncjob_annotation_fontsize": 9,
- "cncjob_annotation_fontcolor": '#990000',
- # NCC Tool
- "tools_ncctools": "0.0393701, 0.019685",
- "tools_nccorder": 'rev',
- "tools_nccoverlap": 0.015748,
- "tools_nccmargin": 0.0393701,
- "tools_nccmethod": "seed",
- "tools_nccconnect": True,
- "tools_ncccontour": True,
- "tools_nccrest": False,
- "tools_ncc_offset_choice": False,
- "tools_ncc_offset_value": 0.0000,
- "tools_nccref": 'itself',
- "tools_ncc_plotting": 'normal',
- "tools_nccmilling_type": 'cl',
- "tools_ncctool_type": 'V',
- "tools_ncccutz": -0.001968504,
- "tools_ncctipdia": 0.00393701,
- "tools_ncctipangle": 30,
- # Cutout Tool
- "tools_cutouttooldia": 0.0944882,
- "tools_cutoutkind": "single",
- "tools_cutoutmargin": 0.00393701,
- "tools_cutoutgapsize": 0.15748,
- "tools_gaps_ff": "4",
- "tools_cutout_convexshape": False,
- # Paint Tool
- "tools_painttooldia": 0.023622,
- "tools_paintorder": 'rev',
- "tools_paintoverlap": 0.015748,
- "tools_paintmargin": 0.0,
- "tools_paintmethod": "seed",
- "tools_selectmethod": "all",
- "tools_pathconnect": True,
- "tools_paintcontour": True,
- "tools_paint_plotting": 'normal',
- # 2-Sided Tool
- "tools_2sided_mirror_axis": "X",
- "tools_2sided_axis_loc": "point",
- "tools_2sided_drilldia": 0.11811,
- # Film Tool
- "tools_film_type": 'neg',
- "tools_film_boundary": 0.0393701,
- "tools_film_scale": 0,
- "tools_film_color": '#000000',
- # Panel Tool
- "tools_panelize_spacing_columns": 0,
- "tools_panelize_spacing_rows": 0,
- "tools_panelize_columns": 1,
- "tools_panelize_rows": 1,
- "tools_panelize_constrain": False,
- "tools_panelize_constrainx": 0.0,
- "tools_panelize_constrainy": 0.0,
- "tools_panelize_panel_type": 'gerber',
- # Calculators Tool
- "tools_calc_vshape_tip_dia": 0.007874,
- "tools_calc_vshape_tip_angle": 30,
- "tools_calc_vshape_cut_z": 0.000787,
- "tools_calc_electro_length": 10.0,
- "tools_calc_electro_width": 10.0,
- "tools_calc_electro_cdensity": 13.0,
- "tools_calc_electro_growth": 10.0,
- # Transform Tool
- "tools_transform_rotate": 90,
- "tools_transform_skew_x": 0.0,
- "tools_transform_skew_y": 0.0,
- "tools_transform_scale_x": 1.0,
- "tools_transform_scale_y": 1.0,
- "tools_transform_scale_link": True,
- "tools_transform_scale_reference": True,
- "tools_transform_offset_x": 0.0,
- "tools_transform_offset_y": 0.0,
- "tools_transform_mirror_reference": False,
- "tools_transform_mirror_point": (0, 0),
- # SolderPaste Tool
- "tools_solderpaste_tools": "0.0393701, 0.011811",
- "tools_solderpaste_new": 0.011811,
- "tools_solderpaste_z_start": 0.00019685039,
- "tools_solderpaste_z_dispense": 0.00393701,
- "tools_solderpaste_z_stop": 0.00019685039,
- "tools_solderpaste_z_travel": 0.00393701,
- "tools_solderpaste_z_toolchange": 0.0393701,
- "tools_solderpaste_xy_toolchange": "0.0, 0.0",
- "tools_solderpaste_frxy": 3.0,
- "tools_solderpaste_frz": 3.0,
- "tools_solderpaste_frz_dispense": 0.0393701,
- "tools_solderpaste_speedfwd": 20,
- "tools_solderpaste_dwellfwd": 1,
- "tools_solderpaste_speedrev": 10,
- "tools_solderpaste_dwellrev": 1,
- "tools_solderpaste_pp": 'Paste_1',
- # Subtract Tool
- "tools_sub_close_paths": True,
- # Utilities
- # file associations
- "fa_excellon": 'drd, drl, exc, ncd, tap, xln',
- "fa_gcode": 'cnc, din, dnc, ecs, eia, fan, fgc, fnc, gc, gcd, gcode, h, hnc, i, min, mpf, mpr, nc, ncc, '
- 'ncg, ncp, out, plt, ply, rol, sbp, tap, xpi',
- "fa_gerber": 'art, bot, bsm, cmp, crc, crs, dim, g4, gb0, gb1, gb2, gb3, gb5, gb6, gb7, gb8, gb9, gbd, '
- 'gbl, gbo, gbp, gbr, gbs, gdo, ger, gko, gm1, gm2, gm3, grb, gtl, gto, gtp, gts, ly15, ly2, '
- 'mil, pho, plc, pls, smb, smt, sol, spb, spt, ssb, sst, stc, sts, top, tsm',
- # Keyword list
- "util_autocomplete_keywords": 'Desktop, Documents, FlatConfig, FlatPrj, Marius, My Documents, Paste_1, '
- 'Repetier, Roland_MDX_20, Users, Toolchange_Custom, Toolchange_Probe_MACH3, '
- 'Toolchange_manual, Users, all, angle_x, angle_y, axis, auto, axisoffset, '
- 'box, center_x, center_y, columns, combine, connect, contour, default, '
- 'depthperpass, dia, diatol, dist, drilled_dias, drillz, dwell, dwelltime, '
- 'feedrate_z, grbl_11, grbl_laser, gridoffsety, gridx, gridy, has_offset, '
- 'holes, hpgl, iso_type, line_xyz, margin, marlin, method, milled_dias, '
- 'minoffset, multidepth, name, offset, opt_type, order, outname, overlap, '
- 'passes, postamble, pp, ppname_e, ppname_g, preamble, radius, ref, rest, '
- 'rows, shellvar_, scale_factor, spacing_columns, spacing_rows, spindlespeed, '
- 'toolchange_xy, use_threads, value, x, x0, x1, y, y0, y1, z_cut, z_move',
- "script_autocompleter": True,
- "script_text": "",
- "script_plot": True,
- "script_source_file": "",
- "document_autocompleter": False,
- "document_text": "",
- "document_plot": True,
- "document_source_file": "",
- "document_font_color": '#000000',
- "document_sel_color": '#0055ff',
- "document_font_size": 6,
- "document_tab_size": 80,
- })
- # ############################################################
- # ############### Load defaults from file ####################
- # ############################################################
- if user_defaults:
- self.load_defaults(filename='current_defaults')
- # #############################################################
- # ############## APPLY APP LANGUAGE ###########################
- # #############################################################
- ret_val = fcTranslate.apply_language('strings')
- if ret_val == "no language":
- self.inform.emit('[ERROR] %s' %
- _("Could not find the Language files. The App strings are missing."))
- log.debug("Could not find the Language files. The App strings are missing.")
- else:
- # make the current language the current selection on the language combobox
- self.ui.general_defaults_form.general_app_group.language_cb.setCurrentText(ret_val)
- log.debug("App.__init__() --> Applied %s language." % str(ret_val).capitalize())
- # ##############################################################
- # ################# CREATE UNIQUE SERIAL NUMBER ################
- # ##############################################################
- chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
- if self.defaults['global_serial'] == 0 or len(str(self.defaults['global_serial'])) < 10:
- self.defaults['global_serial'] = ''.join([random.choice(chars) for __ in range(20)])
- self.save_defaults(silent=True, first_time=True)
- self.propagate_defaults(silent=True)
- self.restore_main_win_geom()
- # def auto_save_defaults():
- # try:
- # self.save_defaults(silent=True)
- # self.propagate_defaults(silent=True)
- # finally:
- # QtCore.QTimer.singleShot(self.defaults["global_defaults_save_period_ms"], auto_save_defaults)
- # the following lines activates automatic defaults save
- # if user_defaults:
- # QtCore.QTimer.singleShot(self.defaults["global_defaults_save_period_ms"], auto_save_defaults)
- # self.options_form = PreferencesUI()
- self.options_form_fields = {
- "units": self.ui.general_options_form.general_app_group.units_radio,
- "global_gridx": self.ui.general_options_form.general_gui_group.gridx_entry,
- "global_gridy": self.ui.general_options_form.general_gui_group.gridy_entry,
- "global_snap_max": self.ui.general_options_form.general_gui_group.snap_max_dist_entry,
- "gerber_plot": self.ui.gerber_options_form.gerber_gen_group.plot_cb,
- "gerber_solid": self.ui.gerber_options_form.gerber_gen_group.solid_cb,
- "gerber_multicolored": self.ui.gerber_options_form.gerber_gen_group.multicolored_cb,
- "gerber_isotooldia": self.ui.gerber_options_form.gerber_opt_group.iso_tool_dia_entry,
- "gerber_isopasses": self.ui.gerber_options_form.gerber_opt_group.iso_width_entry,
- "gerber_isooverlap": self.ui.gerber_options_form.gerber_opt_group.iso_overlap_entry,
- "gerber_combine_passes": self.ui.gerber_options_form.gerber_opt_group.combine_passes_cb,
- "gerber_noncoppermargin": self.ui.gerber_options_form.gerber_opt_group.noncopper_margin_entry,
- "gerber_noncopperrounded": self.ui.gerber_options_form.gerber_opt_group.noncopper_rounded_cb,
- "gerber_bboxmargin": self.ui.gerber_options_form.gerber_opt_group.bbmargin_entry,
- "gerber_bboxrounded": self.ui.gerber_options_form.gerber_opt_group.bbrounded_cb,
- "excellon_plot": self.ui.excellon_options_form.excellon_gen_group.plot_cb,
- "excellon_solid": self.ui.excellon_options_form.excellon_gen_group.solid_cb,
- "excellon_format_upper_in": self.ui.excellon_options_form.excellon_gen_group.excellon_format_upper_in_entry,
- "excellon_format_lower_in": self.ui.excellon_options_form.excellon_gen_group.excellon_format_lower_in_entry,
- "excellon_format_upper_mm": self.ui.excellon_options_form.excellon_gen_group.excellon_format_upper_mm_entry,
- "excellon_format_lower_mm": self.ui.excellon_options_form.excellon_gen_group.excellon_format_lower_mm_entry,
- "excellon_zeros": self.ui.excellon_options_form.excellon_gen_group.excellon_zeros_radio,
- "excellon_units": self.ui.excellon_options_form.excellon_gen_group.excellon_units_radio,
- "excellon_optimization_type": self.ui.excellon_options_form.excellon_gen_group.excellon_optimization_radio,
- "excellon_drillz": self.ui.excellon_options_form.excellon_opt_group.cutz_entry,
- "excellon_travelz": self.ui.excellon_options_form.excellon_opt_group.travelz_entry,
- "excellon_endz": self.ui.excellon_options_form.excellon_opt_group.eendz_entry,
- "excellon_feedrate": self.ui.excellon_options_form.excellon_opt_group.feedrate_entry,
- "excellon_spindlespeed": self.ui.excellon_options_form.excellon_opt_group.spindlespeed_entry,
- "excellon_dwell": self.ui.excellon_options_form.excellon_opt_group.dwell_cb,
- "excellon_dwelltime": self.ui.excellon_options_form.excellon_opt_group.dwelltime_entry,
- "excellon_toolchange": self.ui.excellon_options_form.excellon_opt_group.toolchange_cb,
- "excellon_toolchangez": self.ui.excellon_options_form.excellon_opt_group.toolchangez_entry,
- "excellon_tooldia": self.ui.excellon_options_form.excellon_opt_group.tooldia_entry,
- "excellon_ppname_e": self.ui.excellon_options_form.excellon_opt_group.pp_excellon_name_cb,
- "excellon_feedrate_rapid": self.ui.excellon_options_form.excellon_adv_opt_group.feedrate_rapid_entry,
- "excellon_toolchangexy": self.ui.excellon_options_form.excellon_adv_opt_group.toolchangexy_entry,
- "excellon_f_plunge": self.ui.excellon_options_form.excellon_adv_opt_group.fplunge_cb,
- "excellon_startz": self.ui.excellon_options_form.excellon_adv_opt_group.estartz_entry,
- "excellon_spindledir": self.ui.excellon_options_form.excellon_adv_opt_group.spindledir_radio,
- "geometry_plot": self.ui.geometry_options_form.geometry_gen_group.plot_cb,
- "geometry_cnctooldia": self.ui.geometry_options_form.geometry_gen_group.cnctooldia_entry,
- "geometry_cutz": self.ui.geometry_options_form.geometry_opt_group.cutz_entry,
- "geometry_travelz": self.ui.geometry_options_form.geometry_opt_group.travelz_entry,
- "geometry_feedrate": self.ui.geometry_options_form.geometry_opt_group.cncfeedrate_entry,
- "geometry_feedrate_z": self.ui.geometry_options_form.geometry_opt_group.cncplunge_entry,
- "geometry_spindlespeed": self.ui.geometry_options_form.geometry_opt_group.cncspindlespeed_entry,
- "geometry_dwell": self.ui.geometry_options_form.geometry_opt_group.dwell_cb,
- "geometry_dwelltime": self.ui.geometry_options_form.geometry_opt_group.dwelltime_entry,
- "geometry_ppname_g": self.ui.geometry_options_form.geometry_opt_group.pp_geometry_name_cb,
- "geometry_toolchange": self.ui.geometry_options_form.geometry_opt_group.toolchange_cb,
- "geometry_toolchangez": self.ui.geometry_options_form.geometry_opt_group.toolchangez_entry,
- "geometry_endz": self.ui.geometry_options_form.geometry_opt_group.gendz_entry,
- "geometry_depthperpass": self.ui.geometry_options_form.geometry_opt_group.depthperpass_entry,
- "geometry_multidepth": self.ui.geometry_options_form.geometry_opt_group.multidepth_cb,
- "geometry_segx": self.ui.geometry_options_form.geometry_adv_opt_group.segx_entry,
- "geometry_segy": self.ui.geometry_options_form.geometry_adv_opt_group.segy_entry,
- "geometry_feedrate_rapid": self.ui.geometry_options_form.geometry_adv_opt_group.cncfeedrate_rapid_entry,
- "geometry_f_plunge": self.ui.geometry_options_form.geometry_adv_opt_group.fplunge_cb,
- "geometry_spindledir": self.ui.geometry_options_form.geometry_adv_opt_group.spindledir_radio,
- "geometry_toolchangexy": self.ui.geometry_options_form.geometry_adv_opt_group.toolchangexy_entry,
- "geometry_startz": self.ui.geometry_options_form.geometry_adv_opt_group.gstartz_entry,
- "geometry_extracut": self.ui.geometry_options_form.geometry_adv_opt_group.extracut_cb,
- "cncjob_plot": self.ui.cncjob_options_form.cncjob_gen_group.plot_cb,
- "cncjob_tooldia": self.ui.cncjob_options_form.cncjob_gen_group.tooldia_entry,
- "cncjob_prepend": self.ui.cncjob_options_form.cncjob_opt_group.prepend_text,
- "cncjob_append": self.ui.cncjob_options_form.cncjob_opt_group.append_text,
- "tools_ncctools": self.ui.tools_options_form.tools_ncc_group.ncc_tool_dia_entry,
- "tools_nccoverlap": self.ui.tools_options_form.tools_ncc_group.ncc_overlap_entry,
- "tools_nccmargin": self.ui.tools_options_form.tools_ncc_group.ncc_margin_entry,
- "tools_cutouttooldia": self.ui.tools_options_form.tools_cutout_group.cutout_tooldia_entry,
- "tools_cutoutmargin": self.ui.tools_options_form.tools_cutout_group.cutout_margin_entry,
- "tools_cutoutgapsize": self.ui.tools_options_form.tools_cutout_group.cutout_gap_entry,
- "tools_gaps_ff": self.ui.tools_options_form.tools_cutout_group.gaps_combo,
- "tools_painttooldia": self.ui.tools_options_form.tools_paint_group.painttooldia_entry,
- "tools_paintoverlap": self.ui.tools_options_form.tools_paint_group.paintoverlap_entry,
- "tools_paintmargin": self.ui.tools_options_form.tools_paint_group.paintmargin_entry,
- "tools_paintmethod": self.ui.tools_options_form.tools_paint_group.paintmethod_combo,
- "tools_selectmethod": self.ui.tools_options_form.tools_paint_group.selectmethod_combo,
- "tools_pathconnect": self.ui.tools_options_form.tools_paint_group.pathconnect_cb,
- "tools_paintcontour": self.ui.tools_options_form.tools_paint_group.contour_cb,
- "tools_2sided_mirror_axis": self.ui.tools_options_form.tools_2sided_group.mirror_axis_radio,
- "tools_2sided_axis_loc": self.ui.tools_options_form.tools_2sided_group.axis_location_radio,
- "tools_2sided_drilldia": self.ui.tools_options_form.tools_2sided_group.drill_dia_entry,
- "tools_film_type": self.ui.tools_options_form.tools_film_group.film_type_radio,
- "tools_film_boundary": self.ui.tools_options_form.tools_film_group.film_boundary_entry,
- "tools_film_scale": self.ui.tools_options_form.tools_film_group.film_scale_entry,
- "tools_panelize_spacing_columns": self.ui.tools_options_form.tools_panelize_group.pspacing_columns,
- "tools_panelize_spacing_rows": self.ui.tools_options_form.tools_panelize_group.pspacing_rows,
- "tools_panelize_columns": self.ui.tools_options_form.tools_panelize_group.pcolumns,
- "tools_panelize_rows": self.ui.tools_options_form.tools_panelize_group.prows,
- "tools_panelize_constrain": self.ui.tools_options_form.tools_panelize_group.pconstrain_cb,
- "tools_panelize_constrainx": self.ui.tools_options_form.tools_panelize_group.px_width_entry,
- "tools_panelize_constrainy": self.ui.tools_options_form.tools_panelize_group.py_height_entry
- }
- for name in list(self.postprocessors.keys()):
- self.ui.geometry_options_form.geometry_opt_group.pp_geometry_name_cb.addItem(name)
- self.ui.excellon_options_form.excellon_opt_group.pp_excellon_name_cb.addItem(name)
- self.options = LoudDict()
- self.options.set_change_callback(self.on_options_dict_change)
- self.options.update({
- "units": "IN",
- "global_gridx": 1.0,
- "global_gridy": 1.0,
- "global_snap_max": 0.05,
- "global_background_timeout": 300000, # Default value is 5 minutes
- "global_verbose_error_level": 0, # Shell verbosity:
- # 0 = default(python trace only for unknown errors),
- # 1 = show trace(show trace allways), 2 = (For the future).
- "gerber_plot": True,
- "gerber_solid": True,
- "gerber_multicolored": False,
- "gerber_isotooldia": 0.016,
- "gerber_isopasses": 1,
- "gerber_isooverlap": 0.15,
- "gerber_combine_passes": True,
- "gerber_noncoppermargin": 0.0,
- "gerber_noncopperrounded": False,
- "gerber_bboxmargin": 0.0,
- "gerber_bboxrounded": False,
- "excellon_plot": True,
- "excellon_solid": False,
- "excellon_format_upper_in": 2,
- "excellon_format_lower_in": 4,
- "excellon_format_upper_mm": 3,
- "excellon_format_lower_mm": 3,
- "excellon_units": 'INCH',
- "excellon_optimization_type": 'B',
- "excellon_search_time": 3,
- "excellon_zeros": "L",
- "excellon_drillz": -0.1,
- "excellon_travelz": 0.1,
- "excellon_feedrate": 3.0,
- "excellon_feedrate_rapid": 3.0,
- "excellon_spindlespeed": None,
- "excellon_spindledir": 'CW',
- "excellon_dwell": True,
- "excellon_dwelltime": 1000,
- "excellon_toolchange": False,
- "excellon_toolchangez": 1.0,
- "excellon_toolchangexy": "0.0, 0.0",
- "excellon_tooldia": 0.016,
- "excellon_ppname_e": 'default',
- "excellon_f_plunge": False,
- "excellon_startz": None,
- "excellon_endz": 2.0,
- "geometry_plot": True,
- "geometry_segx": 0.0,
- "geometry_segy": 0.0,
- "geometry_cutz": -0.002,
- "geometry_vtipdia": 0.1,
- "geometry_vtipangle": 30,
- "geometry_travelz": 0.1,
- "geometry_feedrate": 3.0,
- "geometry_feedrate_z": 3.0,
- "geometry_feedrate_rapid": 3.0,
- "geometry_spindlespeed": None,
- "geometry_spindledir": 'CW',
- "geometry_dwell": True,
- "geometry_dwelltime": 1000,
- "geometry_cnctooldia": 0.016,
- "geometry_toolchange": False,
- "geometry_toolchangez": 2.0,
- "geometry_toolchangexy": "0.0, 0.0",
- "geometry_startz": None,
- "geometry_endz": 2.0,
- "geometry_ppname_g": "default",
- "geometry_f_plunge": False,
- "geometry_depthperpass": 0.002,
- "geometry_multidepth": False,
- "geometry_extracut": False,
- "cncjob_plot": True,
- "cncjob_tooldia": 0.016,
- "cncjob_prepend": "",
- "cncjob_append": "",
- "tools_ncctools": "1.0, 0.5",
- "tools_nccoverlap": 0.4,
- "tools_nccmargin": 1,
- "tools_cutouttooldia": 0.07,
- "tools_cutoutmargin": 0.1,
- "tools_cutoutgapsize": 0.15,
- "tools_gaps_ff": "8",
- "tools_painttooldia": 0.07,
- "tools_paintoverlap": 0.15,
- "tools_paintmargin": 0.0,
- "tools_paintmethod": "seed",
- "tools_selectmethod": "single",
- "tools_pathconnect": True,
- "tools_paintcontour": True,
- "tools_2sided_mirror_axis": "X",
- "tools_2sided_axis_loc": 'point',
- "tools_2sided_drilldia": 1,
- "tools_film_type": 'neg',
- "tools_film_boundary": 1,
- "tools_film_scale": 0,
- "tools_panelize_spacing_columns": 0,
- "tools_panelize_spacing_rows": 0,
- "tools_panelize_columns": 1,
- "tools_panelize_rows": 1,
- "tools_panelize_constrain": False,
- "tools_panelize_constrainx": 0.0,
- "tools_panelize_constrainy": 0.0,
- "script_text": "",
- "script_plot": False,
- "script_source_file": "",
- "document_text": "",
- "document_plot": False,
- "document_source_file": "",
- })
- self.options.update(self.defaults) # Copy app defaults to project options
- self.gen_form = None
- self.ger_form = None
- self.exc_form = None
- self.geo_form = None
- self.cnc_form = None
- self.tools_form = None
- self.fa_form = None
- self.on_options_combo_change(0) # Will show the initial form
- # ################################
- # ### Initialize the color box's color in Preferences -> Global -> Color
- # Init Plot Colors
- self.ui.general_defaults_form.general_gui_group.pf_color_entry.set_value(self.defaults['global_plot_fill'])
- self.ui.general_defaults_form.general_gui_group.pf_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_plot_fill'])[:7])
- self.ui.general_defaults_form.general_gui_group.pf_color_alpha_spinner.set_value(
- int(self.defaults['global_plot_fill'][7:9], 16))
- self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.setValue(
- int(self.defaults['global_plot_fill'][7:9], 16))
- self.ui.general_defaults_form.general_gui_group.pl_color_entry.set_value(self.defaults['global_plot_line'])
- self.ui.general_defaults_form.general_gui_group.pl_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_plot_line'])[:7])
- # Init Left-Right Selection colors
- self.ui.general_defaults_form.general_gui_group.sf_color_entry.set_value(self.defaults['global_sel_fill'])
- self.ui.general_defaults_form.general_gui_group.sf_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_sel_fill'])[:7])
- self.ui.general_defaults_form.general_gui_group.sf_color_alpha_spinner.set_value(
- int(self.defaults['global_sel_fill'][7:9], 16))
- self.ui.general_defaults_form.general_gui_group.sf_color_alpha_slider.setValue(
- int(self.defaults['global_sel_fill'][7:9], 16))
- self.ui.general_defaults_form.general_gui_group.sl_color_entry.set_value(self.defaults['global_sel_line'])
- self.ui.general_defaults_form.general_gui_group.sl_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_sel_line'])[:7])
- # Init Right-Left Selection colors
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.set_value(
- self.defaults['global_alt_sel_fill'])
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_alt_sel_fill'])[:7])
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_spinner.set_value(
- int(self.defaults['global_sel_fill'][7:9], 16))
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_slider.setValue(
- int(self.defaults['global_sel_fill'][7:9], 16))
- self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.set_value(
- self.defaults['global_alt_sel_line'])
- self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_alt_sel_line'])[:7])
- # Init Draw color and Selection Draw Color
- self.ui.general_defaults_form.general_gui_group.draw_color_entry.set_value(
- self.defaults['global_draw_color'])
- self.ui.general_defaults_form.general_gui_group.draw_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_draw_color'])[:7])
- self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.set_value(
- self.defaults['global_sel_draw_color'])
- self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_sel_draw_color'])[:7])
- # Init Project Items color
- self.ui.general_defaults_form.general_gui_group.proj_color_entry.set_value(
- self.defaults['global_proj_item_color'])
- self.ui.general_defaults_form.general_gui_group.proj_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_proj_item_color'])[:7])
- self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry.set_value(
- self.defaults['global_proj_item_dis_color'])
- self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_proj_item_dis_color'])[:7])
- # Init the Annotation CNC Job color
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry.set_value(
- self.defaults['cncjob_annotation_fontcolor'])
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['cncjob_annotation_fontcolor'])[:7])
- # Init the Tool Film color
- self.ui.tools_defaults_form.tools_film_group.film_color_entry.set_value(
- self.defaults['tools_film_color'])
- self.ui.tools_defaults_form.tools_film_group.film_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['tools_film_color'])[:7])
- # ### End of Data ####
- # ##############################################
- # ######### SETUP OBJECT COLLECTION ############
- # ##############################################
- self.collection = ObjectCollection(self)
- self.ui.project_tab_layout.addWidget(self.collection.view)
- # ### Adjust tabs width ## ##
- # self.collection.view.setMinimumWidth(self.ui.options_scroll_area.widget().sizeHint().width() +
- # self.ui.options_scroll_area.verticalScrollBar().sizeHint().width())
- self.collection.view.setMinimumWidth(290)
- self.log.debug("Finished creating Object Collection.")
- # ###############################################
- # ############# SETUP Plot Area #################
- # ###############################################
- # determine if the Legacy Graphic Engine is to be used or the OpenGL one
- if self.defaults["global_graphic_engine"] == '3D':
- self.is_legacy = False
- else:
- self.is_legacy = True
- # Matplotlib axis
- self.axes = None
- if show_splash:
- self.splash.showMessage(_("FlatCAM is initializing ...\n"
- "Canvas initialization started."),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- start_plot_time = time.time() # debug
- self.plotcanvas = None
- self.app_cursor = None
- self.hover_shapes = None
- self.on_plotcanvas_setup()
- end_plot_time = time.time()
- self.used_time = end_plot_time - start_plot_time
- self.log.debug("Finished Canvas initialization in %s seconds." % str(self.used_time))
- if show_splash:
- self.splash.showMessage('%s: %ssec' % (_("FlatCAM is initializing ...\n"
- "Canvas initialization started.\n"
- "Canvas initialization finished in"), '%.2f' % self.used_time),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- self.ui.splitter.setStretchFactor(1, 2)
- # #################################################################
- # ####################### SYS TRAY ################################
- # #################################################################
- if self.defaults["global_systray_icon"]:
- self.parent_w = QtWidgets.QWidget()
- if self.cmd_line_headless == 1:
- self.trayIcon = FlatCAMSystemTray(app=self, icon=QtGui.QIcon('share/flatcam_icon32_green.png'),
- headless=True,
- parent=self.parent_w)
- else:
- self.trayIcon = FlatCAMSystemTray(app=self, icon=QtGui.QIcon('share/flatcam_icon32_green.png'),
- parent=self.parent_w)
- # ###############################################
- # ############# Worker SETUP ####################
- # ###############################################
- if self.defaults["global_worker_number"]:
- self.workers = WorkerStack(workers_number=int(self.defaults["global_worker_number"]))
- else:
- self.workers = WorkerStack(workers_number=2)
- self.worker_task.connect(self.workers.add_task)
- self.log.debug("Finished creating Workers crew.")
- # ################################################
- # ############### Activity Monitor ###############
- # ################################################
- if self.defaults["global_activity_icon"] == "Ball green":
- icon = 'share/active_2_static.png'
- movie = "share/active_2.gif"
- elif self.defaults["global_activity_icon"] == "Ball black":
- icon = 'share/active_static.png'
- movie = "share/active.gif"
- elif self.defaults["global_activity_icon"] == "Arrow green":
- icon = 'share/active_3_static.png'
- movie = "share/active_3.gif"
- elif self.defaults["global_activity_icon"] == "Eclipse green":
- icon = 'share/active_4_static.png'
- movie = "share/active_4.gif"
- else:
- icon = 'share/active_static.png'
- movie = "share/active.gif"
- self.activity_view = FlatCAMActivityView(icon=icon, movie=movie)
- self.ui.infobar.addWidget(self.activity_view)
- self.proc_container = FCVisibleProcessContainer(self.activity_view)
- # ################################################
- # ############### Signal handling ################
- # ################################################
- # ############# Custom signals ##################
- # signal for displaying messages in status bar
- self.inform.connect(self.info)
- # signal to be called when the app is quiting
- self.app_quit.connect(self.quit_application)
- self.message.connect(self.message_dialog)
- self.progress.connect(self.set_progress_bar)
- # signals that are emitted when object state changes
- self.object_created.connect(self.on_object_created)
- self.object_changed.connect(self.on_object_changed)
- self.object_plotted.connect(self.on_object_plotted)
- self.plots_updated.connect(self.on_plots_updated)
- # signals emitted when file state change
- self.file_opened.connect(self.register_recent)
- self.file_opened.connect(lambda kind, filename: self.register_folder(filename))
- self.file_saved.connect(lambda kind, filename: self.register_save_folder(filename))
- # ############# Standard signals ###################
- # ### Menu
- self.ui.menufilenewproject.triggered.connect(self.on_file_new_click)
- self.ui.menufilenewgeo.triggered.connect(self.new_geometry_object)
- self.ui.menufilenewgrb.triggered.connect(self.new_gerber_object)
- self.ui.menufilenewexc.triggered.connect(self.new_excellon_object)
- self.ui.menufilenewdoc.triggered.connect(self.new_document_object)
- self.ui.menufileopengerber.triggered.connect(self.on_fileopengerber)
- self.ui.menufileopenexcellon.triggered.connect(self.on_fileopenexcellon)
- self.ui.menufileopengcode.triggered.connect(self.on_fileopengcode)
- self.ui.menufileopenproject.triggered.connect(self.on_file_openproject)
- self.ui.menufileopenconfig.triggered.connect(self.on_file_openconfig)
- self.ui.menufilenewscript.triggered.connect(self.on_filenewscript)
- self.ui.menufileopenscript.triggered.connect(self.on_fileopenscript)
- self.ui.menufilerunscript.triggered.connect(self.on_filerunscript)
- self.ui.menufileimportsvg.triggered.connect(lambda: self.on_file_importsvg("geometry"))
- self.ui.menufileimportsvg_as_gerber.triggered.connect(lambda: self.on_file_importsvg("gerber"))
- self.ui.menufileimportdxf.triggered.connect(lambda: self.on_file_importdxf("geometry"))
- self.ui.menufileimportdxf_as_gerber.triggered.connect(lambda: self.on_file_importdxf("gerber"))
- self.ui.menufileexportsvg.triggered.connect(self.on_file_exportsvg)
- self.ui.menufileexportpng.triggered.connect(self.on_file_exportpng)
- self.ui.menufileexportexcellon.triggered.connect(self.on_file_exportexcellon)
- self.ui.menufileexportgerber.triggered.connect(self.on_file_exportgerber)
- self.ui.menufileexportdxf.triggered.connect(self.on_file_exportdxf)
- self.ui.menufilesaveproject.triggered.connect(self.on_file_saveproject)
- self.ui.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas)
- self.ui.menufilesaveprojectcopy.triggered.connect(lambda: self.on_file_saveprojectas(make_copy=True))
- self.ui.menufilesavedefaults.triggered.connect(self.on_file_savedefaults)
- self.ui.menufileexportpref.triggered.connect(self.on_export_preferences)
- self.ui.menufileimportpref.triggered.connect(self.on_import_preferences)
- self.ui.menufile_exit.triggered.connect(self.final_save)
- self.ui.menueditedit.triggered.connect(lambda: self.object2editor())
- self.ui.menueditok.triggered.connect(lambda: self.editor2object())
- self.ui.menuedit_convertjoin.triggered.connect(self.on_edit_join)
- self.ui.menuedit_convertjoinexc.triggered.connect(self.on_edit_join_exc)
- self.ui.menuedit_convertjoingrb.triggered.connect(self.on_edit_join_grb)
- self.ui.menuedit_convert_sg2mg.triggered.connect(self.on_convert_singlegeo_to_multigeo)
- self.ui.menuedit_convert_mg2sg.triggered.connect(self.on_convert_multigeo_to_singlegeo)
- self.ui.menueditdelete.triggered.connect(self.on_delete)
- self.ui.menueditcopyobject.triggered.connect(self.on_copy_object)
- self.ui.menueditconvert_any2geo.triggered.connect(self.convert_any2geo)
- self.ui.menueditconvert_any2gerber.triggered.connect(self.convert_any2gerber)
- self.ui.menueditorigin.triggered.connect(self.on_set_origin)
- self.ui.menueditjump.triggered.connect(self.on_jump_to)
- self.ui.menuedittoggleunits.triggered.connect(self.on_toggle_units_click)
- self.ui.menueditselectall.triggered.connect(self.on_selectall)
- self.ui.menueditpreferences.triggered.connect(self.on_preferences)
- # self.ui.menuoptions_transfer_a2o.triggered.connect(self.on_options_app2object)
- # self.ui.menuoptions_transfer_a2p.triggered.connect(self.on_options_app2project)
- # self.ui.menuoptions_transfer_o2a.triggered.connect(self.on_options_object2app)
- # self.ui.menuoptions_transfer_p2a.triggered.connect(self.on_options_project2app)
- # self.ui.menuoptions_transfer_o2p.triggered.connect(self.on_options_object2project)
- # self.ui.menuoptions_transfer_p2o.triggered.connect(self.on_options_project2object)
- self.ui.menuoptions_transform_rotate.triggered.connect(self.on_rotate)
- self.ui.menuoptions_transform_skewx.triggered.connect(self.on_skewx)
- self.ui.menuoptions_transform_skewy.triggered.connect(self.on_skewy)
- self.ui.menuoptions_transform_flipx.triggered.connect(self.on_flipx)
- self.ui.menuoptions_transform_flipy.triggered.connect(self.on_flipy)
- self.ui.menuoptions_view_source.triggered.connect(self.on_view_source)
- self.ui.menuviewdisableall.triggered.connect(self.disable_all_plots)
- self.ui.menuviewdisableother.triggered.connect(self.disable_other_plots)
- self.ui.menuviewenable.triggered.connect(self.enable_all_plots)
- self.ui.menuview_zoom_fit.triggered.connect(self.on_zoom_fit)
- self.ui.menuview_zoom_in.triggered.connect(self.on_zoom_in)
- self.ui.menuview_zoom_out.triggered.connect(self.on_zoom_out)
- self.ui.menuview_replot.triggered.connect(self.plot_all)
- self.ui.menuview_toggle_code_editor.triggered.connect(self.on_toggle_code_editor)
- self.ui.menuview_toggle_fscreen.triggered.connect(self.on_fullscreen)
- self.ui.menuview_toggle_parea.triggered.connect(self.on_toggle_plotarea)
- self.ui.menuview_toggle_notebook.triggered.connect(self.on_toggle_notebook)
- self.ui.menu_toggle_nb.triggered.connect(self.on_toggle_notebook)
- self.ui.menuview_toggle_grid.triggered.connect(self.on_toggle_grid)
- self.ui.menuview_toggle_axis.triggered.connect(self.on_toggle_axis)
- self.ui.menuview_toggle_workspace.triggered.connect(self.on_workspace_menu)
- self.ui.menutoolshell.triggered.connect(self.on_toggle_shell)
- self.ui.menuhelp_about.triggered.connect(self.on_about)
- self.ui.menuhelp_home.triggered.connect(lambda: webbrowser.open(self.app_url))
- self.ui.menuhelp_manual.triggered.connect(lambda: webbrowser.open(self.manual_url))
- self.ui.menuhelp_report_bug.triggered.connect(lambda: webbrowser.open(self.bug_report_url))
- self.ui.menuhelp_exc_spec.triggered.connect(lambda: webbrowser.open(self.excellon_spec_url))
- self.ui.menuhelp_gerber_spec.triggered.connect(lambda: webbrowser.open(self.gerber_spec_url))
- self.ui.menuhelp_videohelp.triggered.connect(lambda: webbrowser.open(self.video_url))
- self.ui.menuhelp_shortcut_list.triggered.connect(self.on_shortcut_list)
- self.ui.menuprojectenable.triggered.connect(self.on_enable_sel_plots)
- self.ui.menuprojectdisable.triggered.connect(self.on_disable_sel_plots)
- self.ui.menuprojectgeneratecnc.triggered.connect(lambda: self.generate_cnc_job(self.collection.get_selected()))
- self.ui.menuprojectviewsource.triggered.connect(self.on_view_source)
- self.ui.menuprojectcopy.triggered.connect(self.on_copy_object)
- self.ui.menuprojectedit.triggered.connect(self.object2editor)
- self.ui.menuprojectdelete.triggered.connect(self.on_delete)
- self.ui.menuprojectsave.triggered.connect(self.on_project_context_save)
- self.ui.menuprojectproperties.triggered.connect(self.obj_properties)
- # ToolBar signals
- self.connect_toolbar_signals()
- # Notebook signals
- # make the right click on the notebook tab connect to a function
- self.ui.notebook.setupContextMenu()
- self.ui.notebook.addContextMenu(
- _("Detachable Tabs"), self.on_notebook_tab_rmb_click,
- initial_checked=self.defaults["global_tabs_detachable"])
- # activate initial state
- self.on_notebook_tab_rmb_click(self.defaults["global_tabs_detachable"])
- # Context Menu
- self.ui.popmenu_disable.triggered.connect(lambda: self.toggle_plots(self.collection.get_selected()))
- self.ui.popmenu_panel_toggle.triggered.connect(self.on_toggle_notebook)
- self.ui.popmenu_new_geo.triggered.connect(self.new_geometry_object)
- self.ui.popmenu_new_grb.triggered.connect(self.new_gerber_object)
- self.ui.popmenu_new_exc.triggered.connect(self.new_excellon_object)
- self.ui.popmenu_new_prj.triggered.connect(self.on_file_new)
- self.ui.zoomfit.triggered.connect(self.on_zoom_fit)
- self.ui.clearplot.triggered.connect(self.clear_plots)
- self.ui.replot.triggered.connect(self.plot_all)
- self.ui.popmenu_copy.triggered.connect(self.on_copy_object)
- self.ui.popmenu_delete.triggered.connect(self.on_delete)
- self.ui.popmenu_edit.triggered.connect(self.object2editor)
- self.ui.popmenu_save.triggered.connect(lambda: self.editor2object())
- self.ui.popmenu_move.triggered.connect(self.obj_move)
- self.ui.popmenu_properties.triggered.connect(self.obj_properties)
- # Preferences Plot Area TAB
- self.ui.options_combo.activated.connect(self.on_options_combo_change)
- self.ui.pref_save_button.clicked.connect(self.on_save_button)
- self.ui.pref_import_button.clicked.connect(self.on_import_preferences)
- self.ui.pref_export_button.clicked.connect(self.on_export_preferences)
- self.ui.pref_open_button.clicked.connect(self.on_preferences_open_folder)
- # ##############################
- # ### GUI PREFERENCES SIGNALS ##
- # ##############################
- self.ui.general_defaults_form.general_app_group.ge_radio.activated_custom.connect(self.on_app_restart)
- self.ui.general_options_form.general_app_group.units_radio.group_toggle_fn = self.on_toggle_units
- self.ui.general_defaults_form.general_app_group.language_apply_btn.clicked.connect(
- lambda: fcTranslate.on_language_apply_click(self, restart=True)
- )
- self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(
- lambda: self.on_toggle_units(no_pref=False))
- # ###############################################################
- # ################### GUI COLORS SIGNALS ########################
- # ###############################################################
- # Setting plot colors signals
- self.ui.general_defaults_form.general_gui_group.pf_color_entry.editingFinished.connect(
- self.on_pf_color_entry)
- self.ui.general_defaults_form.general_gui_group.pf_color_button.clicked.connect(
- self.on_pf_color_button)
- self.ui.general_defaults_form.general_gui_group.pf_color_alpha_spinner.valueChanged.connect(
- self.on_pf_color_spinner)
- self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.valueChanged.connect(
- self.on_pf_color_slider)
- self.ui.general_defaults_form.general_gui_group.pl_color_entry.editingFinished.connect(
- self.on_pl_color_entry)
- self.ui.general_defaults_form.general_gui_group.pl_color_button.clicked.connect(
- self.on_pl_color_button)
- # Setting selection (left - right) colors signals
- self.ui.general_defaults_form.general_gui_group.sf_color_entry.editingFinished.connect(
- self.on_sf_color_entry)
- self.ui.general_defaults_form.general_gui_group.sf_color_button.clicked.connect(
- self.on_sf_color_button)
- self.ui.general_defaults_form.general_gui_group.sf_color_alpha_spinner.valueChanged.connect(
- self.on_sf_color_spinner)
- self.ui.general_defaults_form.general_gui_group.sf_color_alpha_slider.valueChanged.connect(
- self.on_sf_color_slider)
- self.ui.general_defaults_form.general_gui_group.sl_color_entry.editingFinished.connect(
- self.on_sl_color_entry)
- self.ui.general_defaults_form.general_gui_group.sl_color_button.clicked.connect(
- self.on_sl_color_button)
- # Setting selection (right - left) colors signals
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.editingFinished.connect(
- self.on_alt_sf_color_entry)
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.clicked.connect(
- self.on_alt_sf_color_button)
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_spinner.valueChanged.connect(
- self.on_alt_sf_color_spinner)
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_slider.valueChanged.connect(
- self.on_alt_sf_color_slider)
- self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.editingFinished.connect(
- self.on_alt_sl_color_entry)
- self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.clicked.connect(
- self.on_alt_sl_color_button)
- # Setting Editor Draw colors signals
- self.ui.general_defaults_form.general_gui_group.draw_color_entry.editingFinished.connect(
- self.on_draw_color_entry)
- self.ui.general_defaults_form.general_gui_group.draw_color_button.clicked.connect(
- self.on_draw_color_button)
- self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.editingFinished.connect(
- self.on_sel_draw_color_entry)
- self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.clicked.connect(
- self.on_sel_draw_color_button)
- self.ui.general_defaults_form.general_gui_group.proj_color_entry.editingFinished.connect(
- self.on_proj_color_entry)
- self.ui.general_defaults_form.general_gui_group.proj_color_button.clicked.connect(
- self.on_proj_color_button)
- self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry.editingFinished.connect(
- self.on_proj_color_dis_entry)
- self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.clicked.connect(
- self.on_proj_color_dis_button)
- # ########## workspace setting signals ###########
- self.ui.general_defaults_form.general_gui_group.wk_cb.currentIndexChanged.connect(self.on_workspace_modified)
- self.ui.general_defaults_form.general_gui_group.workspace_cb.stateChanged.connect(self.on_workspace)
- self.ui.general_defaults_form.general_gui_set_group.layout_combo.activated.connect(self.on_layout)
- # #################################################
- # ############ GUI SETTINGS SIGNALS ###############
- # #################################################
- self.ui.general_defaults_form.general_gui_set_group.cursor_radio.activated_custom.connect(self.on_cursor_type)
- # ########## CNC Job related signals #############
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.tc_variable_combo.currentIndexChanged[str].connect(
- self.on_cnc_custom_parameters)
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry.editingFinished.connect(
- self.on_annotation_fontcolor_entry)
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_button.clicked.connect(
- self.on_annotation_fontcolor_button)
- # ########## Tools related signals #############
- self.ui.tools_defaults_form.tools_film_group.film_color_entry.editingFinished.connect(
- self.on_film_color_entry)
- self.ui.tools_defaults_form.tools_film_group.film_color_button.clicked.connect(
- self.on_film_color_button)
- # portability changed signal
- self.ui.general_defaults_form.general_app_group.portability_cb.stateChanged.connect(self.on_portable_checked)
- # Object list
- self.collection.view.activated.connect(self.on_row_activated)
- self.object_status_changed.connect(self.on_collection_updated)
- # Monitor the checkbox from the Application Defaults Tab and show the TCL shell or not depending on it's value
- self.ui.general_defaults_form.general_gui_set_group.shell_startup_cb.clicked.connect(self.on_toggle_shell)
- # Make sure that when the Excellon loading parameters are changed, the change is reflected in the
- # Export Excellon parameters.
- self.ui.excellon_defaults_form.excellon_gen_group.update_excellon_cb.stateChanged.connect(
- self.on_update_exc_export
- )
- # call it once to make sure it is updated at startup
- self.on_update_exc_export(state=self.defaults["excellon_update"])
- # Load the defaults values into the Excellon Format and Excellon Zeros fields
- self.ui.excellon_defaults_form.excellon_opt_group.excellon_defaults_button.clicked.connect(
- self.on_excellon_defaults_button)
- # Load the defaults values into the Excellon Format and Excellon Zeros fields
- self.ui.excellon_options_form.excellon_opt_group.excellon_defaults_button.clicked.connect(
- self.on_excellon_options_button)
- # when there are arguments at application startup this get launched
- self.args_at_startup[list].connect(self.on_startup_args)
- # ##############################################################
- # ############### FILE ASSOCIATIONS SIGNALS ####################
- # ##############################################################
- self.ui.util_defaults_form.fa_excellon_group.restore_btn.clicked.connect(
- lambda: self.restore_extensions(ext_type='excellon'))
- self.ui.util_defaults_form.fa_gcode_group.restore_btn.clicked.connect(
- lambda: self.restore_extensions(ext_type='gcode'))
- self.ui.util_defaults_form.fa_gerber_group.restore_btn.clicked.connect(
- lambda: self.restore_extensions(ext_type='gerber'))
- self.ui.util_defaults_form.fa_excellon_group.del_all_btn.clicked.connect(
- lambda: self.delete_all_extensions(ext_type='excellon'))
- self.ui.util_defaults_form.fa_gcode_group.del_all_btn.clicked.connect(
- lambda: self.delete_all_extensions(ext_type='gcode'))
- self.ui.util_defaults_form.fa_gerber_group.del_all_btn.clicked.connect(
- lambda: self.delete_all_extensions(ext_type='gerber'))
- self.ui.util_defaults_form.fa_excellon_group.add_btn.clicked.connect(
- lambda: self.add_extension(ext_type='excellon'))
- self.ui.util_defaults_form.fa_gcode_group.add_btn.clicked.connect(
- lambda: self.add_extension(ext_type='gcode'))
- self.ui.util_defaults_form.fa_gerber_group.add_btn.clicked.connect(
- lambda: self.add_extension(ext_type='gerber'))
- self.ui.util_defaults_form.fa_excellon_group.del_btn.clicked.connect(
- lambda: self.del_extension(ext_type='excellon'))
- self.ui.util_defaults_form.fa_gcode_group.del_btn.clicked.connect(
- lambda: self.del_extension(ext_type='gcode'))
- self.ui.util_defaults_form.fa_gerber_group.del_btn.clicked.connect(
- lambda: self.del_extension(ext_type='gerber'))
- # connect the 'Apply' buttons from the Preferences/File Associations
- self.ui.util_defaults_form.fa_excellon_group.exc_list_btn.clicked.connect(
- lambda: self.on_register_files(obj_type='excellon'))
- self.ui.util_defaults_form.fa_gcode_group.gco_list_btn.clicked.connect(
- lambda: self.on_register_files(obj_type='gcode'))
- self.ui.util_defaults_form.fa_gerber_group.grb_list_btn.clicked.connect(
- lambda: self.on_register_files(obj_type='gerber'))
- # ##############################################################
- # ###################### KEYWORDS SIGNALS ######################
- # ##############################################################
- self.ui.util_defaults_form.kw_group.restore_btn.clicked.connect(
- lambda: self.restore_extensions(ext_type='keyword'))
- self.ui.util_defaults_form.kw_group.del_all_btn.clicked.connect(
- lambda: self.delete_all_extensions(ext_type='keyword'))
- self.ui.util_defaults_form.kw_group.add_btn.clicked.connect(
- lambda: self.add_extension(ext_type='keyword'))
- self.ui.util_defaults_form.kw_group.del_btn.clicked.connect(
- lambda: self.del_extension(ext_type='keyword'))
- # splash screen button signal
- self.ui.general_defaults_form.general_gui_set_group.splash_cb.stateChanged.connect(self.on_splash_changed)
- # connect the abort_all_tasks related slots to the related signals
- self.proc_container.idle_flag.connect(self.app_is_idle)
- # #####################################################################################
- # ########### FINISHED CONNECTING SIGNALS #############################################
- # #####################################################################################
- self.log.debug("Finished connecting Signals.")
- # #####################################################################################
- # ########################## Other setups #############################################
- # #####################################################################################
- # to use for tools like Distance tool who depends on the event sources who are changed inside the Editors
- # depending on from where those tools are called different actions can be done
- self.call_source = 'app'
- # this is a flag to signal to other tools that the ui tooltab is locked and not accessible
- self.tool_tab_locked = False
- # decide if to show or hide the Notebook side of the screen at startup
- if self.defaults["global_project_at_startup"] is True:
- self.ui.splitter.setSizes([1, 1])
- else:
- self.ui.splitter.setSizes([0, 1])
- # Sets up FlatCAMObj, FCProcess and FCProcessContainer.
- self.setup_obj_classes()
- self.setup_recent_items()
- self.setup_component_editor()
- # this does not work in Legacy Mode
- if self.is_legacy is True:
- self.ui.general_defaults_form.general_gui_group.workspace_cb.setDisabled(True)
- self.ui.general_defaults_form.general_gui_group.workspace_type_lbl.setDisabled(True)
- self.ui.general_defaults_form.general_gui_group.wk_cb.setDisabled(True)
- # #####################################################################################
- # ######################### Auto-complete KEYWORDS ####################################
- # #####################################################################################
- self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle',
- 'aligndrill', 'aligndrillgrid', 'bbox', 'bounding_box', 'clear', 'cncjob', 'cutout',
- 'delete', 'drillcncjob', 'export_gcode', 'export_svg', 'ext', 'exteriors', 'follow',
- 'geo_union', 'geocutout', 'get_names', 'get_sys', 'getsys', 'help', 'import_svg',
- 'interiors', 'isolate', 'join_excellon', 'join_excellons', 'join_geometries',
- 'join_geometry', 'list_sys', 'listsys', 'milld', 'mills', 'milldrills', 'millslots',
- 'mirror', 'ncc',
- 'ncc_clear', 'ncr', 'new', 'new_geometry', 'non_copper_regions', 'offset',
- 'open_excellon', 'open_gcode', 'open_gerber', 'open_project', 'options', 'origin',
- 'paint', 'pan', 'panel', 'panelize', 'plot_all', 'plot_objects', 'quit_flatcam',
- 'save', 'save_project',
- 'save_sys', 'scale', 'set_active', 'set_origin', 'set_sys',
- 'setsys', 'skew', 'subtract_poly', 'subtract_rectangle',
- 'version', 'write_gcode'
- ]
- self.default_keywords = ['Desktop', 'Documents', 'FlatConfig', 'FlatPrj', 'Marius', 'My Documents', 'Paste_1',
- 'Repetier', 'Roland_MDX_20', 'Users', 'Toolchange_Custom', 'Toolchange_Probe_MACH3',
- 'Toolchange_manual', 'Users', 'all', 'angle_x', 'angle_y', 'auto', 'axis',
- 'axisoffset',
- 'box', 'center_x', 'center_y', 'columns', 'combine', 'connect', 'contour', 'default',
- 'depthperpass', 'dia', 'diatol', 'dist', 'drilled_dias', 'drillz', 'dwell',
- 'dwelltime', 'feedrate_z', 'grbl_11', 'grbl_laser', 'gridoffsety', 'gridx', 'gridy',
- 'has_offset', 'holes', 'hpgl', 'iso_type', 'line_xyz', 'margin', 'marlin', 'method',
- 'milled_dias', 'minoffset', 'multidepth', 'name', 'offset', 'opt_type', 'order',
- 'outname', 'overlap', 'passes', 'postamble', 'pp', 'ppname_e', 'ppname_g',
- 'preamble', 'radius', 'ref', 'rest', 'rows', 'shellvar_', 'scale_factor',
- 'spacing_columns',
- 'spacing_rows', 'spindlespeed', 'toolchange_xy', 'use_threads', 'value', 'x',
- 'x0', 'x1', 'y', 'y0', 'y1', 'z_cut', 'z_move'
- ]
- self.tcl_keywords = [
- 'after', 'append', 'apply', 'argc', 'argv', 'argv0', 'array', 'attemptckalloc', 'attemptckrealloc',
- 'auto_execok', 'auto_import', 'auto_load', 'auto_mkindex', 'auto_path', 'auto_qualify', 'auto_reset',
- 'bgerror', 'binary', 'break', 'case', 'catch', 'cd', 'chan', 'ckalloc', 'ckfree', 'ckrealloc', 'clock',
- 'close', 'concat', 'continue', 'coroutine', 'dde', 'dict', 'encoding', 'env', 'eof', 'error', 'errorCode',
- 'errorInfo', 'eval', 'exec', 'exit', 'expr', 'fblocked', 'fconfigure', 'fcopy', 'file', 'fileevent',
- 'filename', 'flush', 'for', 'foreach', 'format', 'gets', 'glob', 'global', 'history', 'http', 'if', 'incr',
- 'info', 'interp', 'join', 'lappend', 'lassign', 'lindex', 'linsert', 'list', 'llength', 'load', 'lrange',
- 'lrepeat', 'lreplace', 'lreverse', 'lsearch', 'lset', 'lsort', 'mathfunc', 'mathop', 'memory', 'msgcat',
- 'my', 'namespace', 'next', 'nextto', 'open', 'package', 'parray', 'pid', 'pkg_mkIndex', 'platform',
- 'proc', 'puts', 'pwd', 're_syntax', 'read', 'refchan', 'regexp', 'registry', 'regsub', 'rename', 'return',
- 'safe', 'scan', 'seek', 'self', 'set', 'socket', 'source', 'split', 'string', 'subst', 'switch',
- 'tailcall', 'Tcl', 'Tcl_Access', 'Tcl_AddErrorInfo', 'Tcl_AddObjErrorInfo', 'Tcl_AlertNotifier',
- 'Tcl_Alloc', 'Tcl_AllocHashEntryProc', 'Tcl_AllocStatBuf', 'Tcl_AllowExceptions', 'Tcl_AppendAllObjTypes',
- 'Tcl_AppendElement', 'Tcl_AppendExportList', 'Tcl_AppendFormatToObj', 'Tcl_AppendLimitedToObj',
- 'Tcl_AppendObjToErrorInfo', 'Tcl_AppendObjToObj', 'Tcl_AppendPrintfToObj', 'Tcl_AppendResult',
- 'Tcl_AppendResultVA', 'Tcl_AppendStringsToObj', 'Tcl_AppendStringsToObjVA', 'Tcl_AppendToObj',
- 'Tcl_AppendUnicodeToObj', 'Tcl_AppInit', 'Tcl_AppInitProc', 'Tcl_ArgvInfo', 'Tcl_AsyncCreate',
- 'Tcl_AsyncDelete', 'Tcl_AsyncInvoke', 'Tcl_AsyncMark', 'Tcl_AsyncProc', 'Tcl_AsyncReady',
- 'Tcl_AttemptAlloc', 'Tcl_AttemptRealloc', 'Tcl_AttemptSetObjLength', 'Tcl_BackgroundError',
- 'Tcl_BackgroundException', 'Tcl_Backslash', 'Tcl_BadChannelOption', 'Tcl_CallWhenDeleted', 'Tcl_Canceled',
- 'Tcl_CancelEval', 'Tcl_CancelIdleCall', 'Tcl_ChannelBlockModeProc', 'Tcl_ChannelBuffered',
- 'Tcl_ChannelClose2Proc', 'Tcl_ChannelCloseProc', 'Tcl_ChannelFlushProc', 'Tcl_ChannelGetHandleProc',
- 'Tcl_ChannelGetOptionProc', 'Tcl_ChannelHandlerProc', 'Tcl_ChannelInputProc', 'Tcl_ChannelName',
- 'Tcl_ChannelOutputProc', 'Tcl_ChannelProc', 'Tcl_ChannelSeekProc', 'Tcl_ChannelSetOptionProc',
- 'Tcl_ChannelThreadActionProc', 'Tcl_ChannelTruncateProc', 'Tcl_ChannelType', 'Tcl_ChannelVersion',
- 'Tcl_ChannelWatchProc', 'Tcl_ChannelWideSeekProc', 'Tcl_Chdir', 'Tcl_ClassGetMetadata',
- 'Tcl_ClassSetConstructor', 'Tcl_ClassSetDestructor', 'Tcl_ClassSetMetadata', 'Tcl_ClearChannelHandlers',
- 'Tcl_CloneProc', 'Tcl_Close', 'Tcl_CloseProc', 'Tcl_CmdDeleteProc', 'Tcl_CmdInfo',
- 'Tcl_CmdObjTraceDeleteProc', 'Tcl_CmdObjTraceProc', 'Tcl_CmdProc', 'Tcl_CmdTraceProc',
- 'Tcl_CommandComplete', 'Tcl_CommandTraceInfo', 'Tcl_CommandTraceProc', 'Tcl_CompareHashKeysProc',
- 'Tcl_Concat', 'Tcl_ConcatObj', 'Tcl_ConditionFinalize', 'Tcl_ConditionNotify', 'Tcl_ConditionWait',
- 'Tcl_Config', 'Tcl_ConvertCountedElement', 'Tcl_ConvertElement', 'Tcl_ConvertToType',
- 'Tcl_CopyObjectInstance', 'Tcl_CreateAlias', 'Tcl_CreateAliasObj', 'Tcl_CreateChannel',
- 'Tcl_CreateChannelHandler', 'Tcl_CreateCloseHandler', 'Tcl_CreateCommand', 'Tcl_CreateEncoding',
- 'Tcl_CreateEnsemble', 'Tcl_CreateEventSource', 'Tcl_CreateExitHandler', 'Tcl_CreateFileHandler',
- 'Tcl_CreateHashEntry', 'Tcl_CreateInterp', 'Tcl_CreateMathFunc', 'Tcl_CreateNamespace',
- 'Tcl_CreateObjCommand', 'Tcl_CreateObjTrace', 'Tcl_CreateSlave', 'Tcl_CreateThread',
- 'Tcl_CreateThreadExitHandler', 'Tcl_CreateTimerHandler', 'Tcl_CreateTrace',
- 'Tcl_CutChannel', 'Tcl_DecrRefCount', 'Tcl_DeleteAssocData', 'Tcl_DeleteChannelHandler',
- 'Tcl_DeleteCloseHandler', 'Tcl_DeleteCommand', 'Tcl_DeleteCommandFromToken', 'Tcl_DeleteEvents',
- 'Tcl_DeleteEventSource', 'Tcl_DeleteExitHandler', 'Tcl_DeleteFileHandler', 'Tcl_DeleteHashEntry',
- 'Tcl_DeleteHashTable', 'Tcl_DeleteInterp', 'Tcl_DeleteNamespace', 'Tcl_DeleteThreadExitHandler',
- 'Tcl_DeleteTimerHandler', 'Tcl_DeleteTrace', 'Tcl_DetachChannel', 'Tcl_DetachPids', 'Tcl_DictObjDone',
- 'Tcl_DictObjFirst', 'Tcl_DictObjGet', 'Tcl_DictObjNext', 'Tcl_DictObjPut', 'Tcl_DictObjPutKeyList',
- 'Tcl_DictObjRemove', 'Tcl_DictObjRemoveKeyList', 'Tcl_DictObjSize', 'Tcl_DiscardInterpState',
- 'Tcl_DiscardResult', 'Tcl_DontCallWhenDeleted', 'Tcl_DoOneEvent', 'Tcl_DoWhenIdle',
- 'Tcl_DriverBlockModeProc', 'Tcl_DriverClose2Proc', 'Tcl_DriverCloseProc', 'Tcl_DriverFlushProc',
- 'Tcl_DriverGetHandleProc', 'Tcl_DriverGetOptionProc', 'Tcl_DriverHandlerProc', 'Tcl_DriverInputProc',
- 'Tcl_DriverOutputProc', 'Tcl_DriverSeekProc', 'Tcl_DriverSetOptionProc', 'Tcl_DriverThreadActionProc',
- 'Tcl_DriverTruncateProc', 'Tcl_DriverWatchProc', 'Tcl_DriverWideSeekProc', 'Tcl_DStringAppend',
- 'Tcl_DStringAppendElement', 'Tcl_DStringEndSublist', 'Tcl_DStringFree', 'Tcl_DStringGetResult',
- 'Tcl_DStringInit', 'Tcl_DStringLength', 'Tcl_DStringResult', 'Tcl_DStringSetLength',
- 'Tcl_DStringStartSublist', 'Tcl_DStringTrunc', 'Tcl_DStringValue', 'Tcl_DumpActiveMemory',
- 'Tcl_DupInternalRepProc', 'Tcl_DuplicateObj', 'Tcl_EncodingConvertProc', 'Tcl_EncodingFreeProc',
- 'Tcl_EncodingType', 'tcl_endOfWord', 'Tcl_Eof', 'Tcl_ErrnoId', 'Tcl_ErrnoMsg', 'Tcl_Eval', 'Tcl_EvalEx',
- 'Tcl_EvalFile', 'Tcl_EvalObjEx', 'Tcl_EvalObjv', 'Tcl_EvalTokens', 'Tcl_EvalTokensStandard', 'Tcl_Event',
- 'Tcl_EventCheckProc', 'Tcl_EventDeleteProc', 'Tcl_EventProc', 'Tcl_EventSetupProc', 'Tcl_EventuallyFree',
- 'Tcl_Exit', 'Tcl_ExitProc', 'Tcl_ExitThread', 'Tcl_Export', 'Tcl_ExposeCommand', 'Tcl_ExprBoolean',
- 'Tcl_ExprBooleanObj', 'Tcl_ExprDouble', 'Tcl_ExprDoubleObj', 'Tcl_ExprLong', 'Tcl_ExprLongObj',
- 'Tcl_ExprObj', 'Tcl_ExprString', 'Tcl_ExternalToUtf', 'Tcl_ExternalToUtfDString', 'Tcl_FileProc',
- 'Tcl_Filesystem', 'Tcl_Finalize', 'Tcl_FinalizeNotifier', 'Tcl_FinalizeThread', 'Tcl_FindCommand',
- 'Tcl_FindEnsemble', 'Tcl_FindExecutable', 'Tcl_FindHashEntry', 'tcl_findLibrary', 'Tcl_FindNamespace',
- 'Tcl_FirstHashEntry', 'Tcl_Flush', 'Tcl_ForgetImport', 'Tcl_Format', 'Tcl_FreeHashEntryProc',
- 'Tcl_FreeInternalRepProc', 'Tcl_FreeParse', 'Tcl_FreeProc', 'Tcl_FreeResult',
- 'Tcl_Free·\xa0Tcl_FreeEncoding', 'Tcl_FSAccess', 'Tcl_FSAccessProc', 'Tcl_FSChdir',
- 'Tcl_FSChdirProc', 'Tcl_FSConvertToPathType', 'Tcl_FSCopyDirectory', 'Tcl_FSCopyDirectoryProc',
- 'Tcl_FSCopyFile', 'Tcl_FSCopyFileProc', 'Tcl_FSCreateDirectory', 'Tcl_FSCreateDirectoryProc',
- 'Tcl_FSCreateInternalRepProc', 'Tcl_FSData', 'Tcl_FSDeleteFile', 'Tcl_FSDeleteFileProc',
- 'Tcl_FSDupInternalRepProc', 'Tcl_FSEqualPaths', 'Tcl_FSEvalFile', 'Tcl_FSEvalFileEx',
- 'Tcl_FSFileAttrsGet', 'Tcl_FSFileAttrsGetProc', 'Tcl_FSFileAttrsSet', 'Tcl_FSFileAttrsSetProc',
- 'Tcl_FSFileAttrStrings', 'Tcl_FSFileSystemInfo', 'Tcl_FSFilesystemPathTypeProc',
- 'Tcl_FSFilesystemSeparatorProc', 'Tcl_FSFreeInternalRepProc', 'Tcl_FSGetCwd', 'Tcl_FSGetCwdProc',
- 'Tcl_FSGetFileSystemForPath', 'Tcl_FSGetInternalRep', 'Tcl_FSGetNativePath', 'Tcl_FSGetNormalizedPath',
- 'Tcl_FSGetPathType', 'Tcl_FSGetTranslatedPath', 'Tcl_FSGetTranslatedStringPath',
- 'Tcl_FSInternalToNormalizedProc', 'Tcl_FSJoinPath', 'Tcl_FSJoinToPath', 'Tcl_FSLinkProc',
- 'Tcl_FSLink·\xa0Tcl_FSListVolumes', 'Tcl_FSListVolumesProc', 'Tcl_FSLoadFile', 'Tcl_FSLoadFileProc',
- 'Tcl_FSLstat', 'Tcl_FSLstatProc', 'Tcl_FSMatchInDirectory', 'Tcl_FSMatchInDirectoryProc',
- 'Tcl_FSMountsChanged', 'Tcl_FSNewNativePath', 'Tcl_FSNormalizePathProc', 'Tcl_FSOpenFileChannel',
- 'Tcl_FSOpenFileChannelProc', 'Tcl_FSPathInFilesystemProc', 'Tcl_FSPathSeparator', 'Tcl_FSRegister',
- 'Tcl_FSRemoveDirectory', 'Tcl_FSRemoveDirectoryProc', 'Tcl_FSRenameFile', 'Tcl_FSRenameFileProc',
- 'Tcl_FSSplitPath', 'Tcl_FSStat', 'Tcl_FSStatProc', 'Tcl_FSUnloadFile', 'Tcl_FSUnloadFileProc',
- 'Tcl_FSUnregister', 'Tcl_FSUtime', 'Tcl_FSUtimeProc', 'Tcl_GetAccessTimeFromStat', 'Tcl_GetAlias',
- 'Tcl_GetAliasObj', 'Tcl_GetAssocData', 'Tcl_GetBignumFromObj', 'Tcl_GetBlocksFromStat',
- 'Tcl_GetBlockSizeFromStat', 'Tcl_GetBoolean', 'Tcl_GetBooleanFromObj', 'Tcl_GetByteArrayFromObj',
- 'Tcl_GetChangeTimeFromStat', 'Tcl_GetChannel', 'Tcl_GetChannelBufferSize', 'Tcl_GetChannelError',
- 'Tcl_GetChannelErrorInterp', 'Tcl_GetChannelHandle', 'Tcl_GetChannelInstanceData', 'Tcl_GetChannelMode',
- 'Tcl_GetChannelName', 'Tcl_GetChannelNames', 'Tcl_GetChannelNamesEx', 'Tcl_GetChannelOption',
- 'Tcl_GetChannelThread', 'Tcl_GetChannelType', 'Tcl_GetCharLength', 'Tcl_GetClassAsObject',
- 'Tcl_GetCommandFromObj', 'Tcl_GetCommandFullName', 'Tcl_GetCommandInfo', 'Tcl_GetCommandInfoFromToken',
- 'Tcl_GetCommandName', 'Tcl_GetCurrentNamespace', 'Tcl_GetCurrentThread', 'Tcl_GetCwd',
- 'Tcl_GetDefaultEncodingDir', 'Tcl_GetDeviceTypeFromStat', 'Tcl_GetDouble', 'Tcl_GetDoubleFromObj',
- 'Tcl_GetEncoding', 'Tcl_GetEncodingFromObj', 'Tcl_GetEncodingName', 'Tcl_GetEncodingNameFromEnvironment',
- 'Tcl_GetEncodingNames', 'Tcl_GetEncodingSearchPath', 'Tcl_GetEnsembleFlags', 'Tcl_GetEnsembleMappingDict',
- 'Tcl_GetEnsembleNamespace', 'Tcl_GetEnsembleParameterList', 'Tcl_GetEnsembleSubcommandList',
- 'Tcl_GetEnsembleUnknownHandler', 'Tcl_GetErrno', 'Tcl_GetErrorLine', 'Tcl_GetFSDeviceFromStat',
- 'Tcl_GetFSInodeFromStat', 'Tcl_GetGlobalNamespace', 'Tcl_GetGroupIdFromStat', 'Tcl_GetHashKey',
- 'Tcl_GetHashValue', 'Tcl_GetHostName', 'Tcl_GetIndexFromObj', 'Tcl_GetIndexFromObjStruct', 'Tcl_GetInt',
- 'Tcl_GetInterpPath', 'Tcl_GetIntFromObj', 'Tcl_GetLinkCountFromStat', 'Tcl_GetLongFromObj',
- 'Tcl_GetMaster', 'Tcl_GetMathFuncInfo', 'Tcl_GetModeFromStat', 'Tcl_GetModificationTimeFromStat',
- 'Tcl_GetNameOfExecutable', 'Tcl_GetNamespaceUnknownHandler', 'Tcl_GetObjectAsClass', 'Tcl_GetObjectCommand',
- 'Tcl_GetObjectFromObj', 'Tcl_GetObjectName', 'Tcl_GetObjectNamespace', 'Tcl_GetObjResult', 'Tcl_GetObjType',
- 'Tcl_GetOpenFile', 'Tcl_GetPathType', 'Tcl_GetRange', 'Tcl_GetRegExpFromObj', 'Tcl_GetReturnOptions',
- 'Tcl_Gets', 'Tcl_GetServiceMode', 'Tcl_GetSizeFromStat', 'Tcl_GetSlave', 'Tcl_GetsObj',
- 'Tcl_GetStackedChannel', 'Tcl_GetStartupScript', 'Tcl_GetStdChannel', 'Tcl_GetString',
- 'Tcl_GetStringFromObj', 'Tcl_GetStringResult', 'Tcl_GetThreadData', 'Tcl_GetTime', 'Tcl_GetTopChannel',
- 'Tcl_GetUniChar', 'Tcl_GetUnicode', 'Tcl_GetUnicodeFromObj', 'Tcl_GetUserIdFromStat', 'Tcl_GetVar',
- 'Tcl_GetVar2', 'Tcl_GetVar2Ex', 'Tcl_GetVersion', 'Tcl_GetWideIntFromObj', 'Tcl_GlobalEval',
- 'Tcl_GlobalEvalObj', 'Tcl_GlobTypeData', 'Tcl_HashKeyType', 'Tcl_HashStats', 'Tcl_HideCommand',
- 'Tcl_IdleProc', 'Tcl_Import', 'Tcl_IncrRefCount', 'Tcl_Init', 'Tcl_InitCustomHashTable',
- 'Tcl_InitHashTable', 'Tcl_InitMemory', 'Tcl_InitNotifier', 'Tcl_InitObjHashTable', 'Tcl_InitStubs',
- 'Tcl_InputBlocked', 'Tcl_InputBuffered', 'tcl_interactive', 'Tcl_Interp', 'Tcl_InterpActive',
- 'Tcl_InterpDeleted', 'Tcl_InterpDeleteProc', 'Tcl_InvalidateStringRep', 'Tcl_IsChannelExisting',
- 'Tcl_IsChannelRegistered', 'Tcl_IsChannelShared', 'Tcl_IsEnsemble', 'Tcl_IsSafe', 'Tcl_IsShared',
- 'Tcl_IsStandardChannel', 'Tcl_JoinPath', 'Tcl_JoinThread', 'tcl_library', 'Tcl_LimitAddHandler',
- 'Tcl_LimitCheck', 'Tcl_LimitExceeded', 'Tcl_LimitGetCommands', 'Tcl_LimitGetGranularity',
- 'Tcl_LimitGetTime', 'Tcl_LimitHandlerDeleteProc', 'Tcl_LimitHandlerProc', 'Tcl_LimitReady',
- 'Tcl_LimitRemoveHandler', 'Tcl_LimitSetCommands', 'Tcl_LimitSetGranularity', 'Tcl_LimitSetTime',
- 'Tcl_LimitTypeEnabled', 'Tcl_LimitTypeExceeded', 'Tcl_LimitTypeReset', 'Tcl_LimitTypeSet',
- 'Tcl_LinkVar', 'Tcl_ListMathFuncs', 'Tcl_ListObjAppendElement', 'Tcl_ListObjAppendList',
- 'Tcl_ListObjGetElements', 'Tcl_ListObjIndex', 'Tcl_ListObjLength', 'Tcl_ListObjReplace',
- 'Tcl_LogCommandInfo', 'Tcl_Main', 'Tcl_MainLoopProc', 'Tcl_MakeFileChannel', 'Tcl_MakeSafe',
- 'Tcl_MakeTcpClientChannel', 'Tcl_MathProc', 'TCL_MEM_DEBUG', 'Tcl_Merge', 'Tcl_MethodCallProc',
- 'Tcl_MethodDeclarerClass', 'Tcl_MethodDeclarerObject', 'Tcl_MethodDeleteProc', 'Tcl_MethodIsPublic',
- 'Tcl_MethodIsType', 'Tcl_MethodName', 'Tcl_MethodType', 'Tcl_MutexFinalize', 'Tcl_MutexLock',
- 'Tcl_MutexUnlock', 'Tcl_NamespaceDeleteProc', 'Tcl_NewBignumObj', 'Tcl_NewBooleanObj',
- 'Tcl_NewByteArrayObj', 'Tcl_NewDictObj', 'Tcl_NewDoubleObj', 'Tcl_NewInstanceMethod', 'Tcl_NewIntObj',
- 'Tcl_NewListObj', 'Tcl_NewLongObj', 'Tcl_NewMethod', 'Tcl_NewObj', 'Tcl_NewObjectInstance',
- 'Tcl_NewStringObj', 'Tcl_NewUnicodeObj', 'Tcl_NewWideIntObj', 'Tcl_NextHashEntry', 'tcl_nonwordchars',
- 'Tcl_NotifierProcs', 'Tcl_NotifyChannel', 'Tcl_NRAddCallback', 'Tcl_NRCallObjProc', 'Tcl_NRCmdSwap',
- 'Tcl_NRCreateCommand', 'Tcl_NREvalObj', 'Tcl_NREvalObjv', 'Tcl_NumUtfChars', 'Tcl_Obj', 'Tcl_ObjCmdProc',
- 'Tcl_ObjectContextInvokeNext', 'Tcl_ObjectContextIsFiltering', 'Tcl_ObjectContextMethod',
- 'Tcl_ObjectContextObject', 'Tcl_ObjectContextSkippedArgs', 'Tcl_ObjectDeleted', 'Tcl_ObjectGetMetadata',
- 'Tcl_ObjectGetMethodNameMapper', 'Tcl_ObjectMapMethodNameProc', 'Tcl_ObjectMetadataDeleteProc',
- 'Tcl_ObjectSetMetadata', 'Tcl_ObjectSetMethodNameMapper', 'Tcl_ObjGetVar2', 'Tcl_ObjPrintf',
- 'Tcl_ObjSetVar2', 'Tcl_ObjType', 'Tcl_OpenCommandChannel', 'Tcl_OpenFileChannel', 'Tcl_OpenTcpClient',
- 'Tcl_OpenTcpServer', 'Tcl_OutputBuffered', 'Tcl_PackageInitProc', 'Tcl_PackageUnloadProc', 'Tcl_Panic',
- 'Tcl_PanicProc', 'Tcl_PanicVA', 'Tcl_ParseArgsObjv', 'Tcl_ParseBraces', 'Tcl_ParseCommand', 'Tcl_ParseExpr',
- 'Tcl_ParseQuotedString', 'Tcl_ParseVar', 'Tcl_ParseVarName', 'tcl_patchLevel', 'tcl_pkgPath',
- 'Tcl_PkgPresent', 'Tcl_PkgPresentEx', 'Tcl_PkgProvide', 'Tcl_PkgProvideEx', 'Tcl_PkgRequire',
- 'Tcl_PkgRequireEx', 'Tcl_PkgRequireProc', 'tcl_platform', 'Tcl_PosixError', 'tcl_precision',
- 'Tcl_Preserve', 'Tcl_PrintDouble', 'Tcl_PutEnv', 'Tcl_QueryTimeProc', 'Tcl_QueueEvent', 'tcl_rcFileName',
- 'Tcl_Read', 'Tcl_ReadChars', 'Tcl_ReadRaw', 'Tcl_Realloc', 'Tcl_ReapDetachedProcs', 'Tcl_RecordAndEval',
- 'Tcl_RecordAndEvalObj', 'Tcl_RegExpCompile', 'Tcl_RegExpExec', 'Tcl_RegExpExecObj', 'Tcl_RegExpGetInfo',
- 'Tcl_RegExpIndices', 'Tcl_RegExpInfo', 'Tcl_RegExpMatch', 'Tcl_RegExpMatchObj', 'Tcl_RegExpRange',
- 'Tcl_RegisterChannel', 'Tcl_RegisterConfig', 'Tcl_RegisterObjType', 'Tcl_Release', 'Tcl_ResetResult',
- 'Tcl_RestoreInterpState', 'Tcl_RestoreResult', 'Tcl_SaveInterpState', 'Tcl_SaveResult', 'Tcl_ScaleTimeProc',
- 'Tcl_ScanCountedElement', 'Tcl_ScanElement', 'Tcl_Seek', 'Tcl_ServiceAll', 'Tcl_ServiceEvent',
- 'Tcl_ServiceModeHook', 'Tcl_SetAssocData', 'Tcl_SetBignumObj', 'Tcl_SetBooleanObj',
- 'Tcl_SetByteArrayLength', 'Tcl_SetByteArrayObj', 'Tcl_SetChannelBufferSize', 'Tcl_SetChannelError',
- 'Tcl_SetChannelErrorInterp', 'Tcl_SetChannelOption', 'Tcl_SetCommandInfo', 'Tcl_SetCommandInfoFromToken',
- 'Tcl_SetDefaultEncodingDir', 'Tcl_SetDoubleObj', 'Tcl_SetEncodingSearchPath', 'Tcl_SetEnsembleFlags',
- 'Tcl_SetEnsembleMappingDict', 'Tcl_SetEnsembleParameterList', 'Tcl_SetEnsembleSubcommandList',
- 'Tcl_SetEnsembleUnknownHandler', 'Tcl_SetErrno', 'Tcl_SetErrorCode', 'Tcl_SetErrorCodeVA',
- 'Tcl_SetErrorLine', 'Tcl_SetExitProc', 'Tcl_SetFromAnyProc', 'Tcl_SetHashValue', 'Tcl_SetIntObj',
- 'Tcl_SetListObj', 'Tcl_SetLongObj', 'Tcl_SetMainLoop', 'Tcl_SetMaxBlockTime',
- 'Tcl_SetNamespaceUnknownHandler', 'Tcl_SetNotifier', 'Tcl_SetObjErrorCode', 'Tcl_SetObjLength',
- 'Tcl_SetObjResult', 'Tcl_SetPanicProc', 'Tcl_SetRecursionLimit', 'Tcl_SetResult', 'Tcl_SetReturnOptions',
- 'Tcl_SetServiceMode', 'Tcl_SetStartupScript', 'Tcl_SetStdChannel', 'Tcl_SetStringObj',
- 'Tcl_SetSystemEncoding', 'Tcl_SetTimeProc', 'Tcl_SetTimer', 'Tcl_SetUnicodeObj', 'Tcl_SetVar',
- 'Tcl_SetVar2', 'Tcl_SetVar2Ex', 'Tcl_SetWideIntObj', 'Tcl_SignalId', 'Tcl_SignalMsg', 'Tcl_Sleep',
- 'Tcl_SourceRCFile', 'Tcl_SpliceChannel', 'Tcl_SplitList', 'Tcl_SplitPath', 'Tcl_StackChannel',
- 'Tcl_StandardChannels', 'tcl_startOfNextWord', 'tcl_startOfPreviousWord', 'Tcl_Stat', 'Tcl_StaticPackage',
- 'Tcl_StringCaseMatch', 'Tcl_StringMatch', 'Tcl_SubstObj', 'Tcl_TakeBignumFromObj', 'Tcl_TcpAcceptProc',
- 'Tcl_Tell', 'Tcl_ThreadAlert', 'Tcl_ThreadQueueEvent', 'Tcl_Time', 'Tcl_TimerProc', 'Tcl_Token',
- 'Tcl_TraceCommand', 'tcl_traceCompile', 'tcl_traceEval', 'Tcl_TraceVar', 'Tcl_TraceVar2',
- 'Tcl_TransferResult', 'Tcl_TranslateFileName', 'Tcl_TruncateChannel', 'Tcl_Ungets', 'Tcl_UniChar',
- 'Tcl_UniCharAtIndex', 'Tcl_UniCharCaseMatch', 'Tcl_UniCharIsAlnum', 'Tcl_UniCharIsAlpha',
- 'Tcl_UniCharIsControl', 'Tcl_UniCharIsDigit', 'Tcl_UniCharIsGraph', 'Tcl_UniCharIsLower',
- 'Tcl_UniCharIsPrint', 'Tcl_UniCharIsPunct', 'Tcl_UniCharIsSpace', 'Tcl_UniCharIsUpper',
- 'Tcl_UniCharIsWordChar', 'Tcl_UniCharLen', 'Tcl_UniCharNcasecmp', 'Tcl_UniCharNcmp', 'Tcl_UniCharToLower',
- 'Tcl_UniCharToTitle', 'Tcl_UniCharToUpper', 'Tcl_UniCharToUtf', 'Tcl_UniCharToUtfDString', 'Tcl_UnlinkVar',
- 'Tcl_UnregisterChannel', 'Tcl_UnsetVar', 'Tcl_UnsetVar2', 'Tcl_UnstackChannel', 'Tcl_UntraceCommand',
- 'Tcl_UntraceVar', 'Tcl_UntraceVar2', 'Tcl_UpdateLinkedVar', 'Tcl_UpdateStringProc', 'Tcl_UpVar',
- 'Tcl_UpVar2', 'Tcl_UtfAtIndex', 'Tcl_UtfBackslash', 'Tcl_UtfCharComplete', 'Tcl_UtfFindFirst',
- 'Tcl_UtfFindLast', 'Tcl_UtfNext', 'Tcl_UtfPrev', 'Tcl_UtfToExternal', 'Tcl_UtfToExternalDString',
- 'Tcl_UtfToLower', 'Tcl_UtfToTitle', 'Tcl_UtfToUniChar', 'Tcl_UtfToUniCharDString', 'Tcl_UtfToUpper',
- 'Tcl_ValidateAllMemory', 'Tcl_Value', 'Tcl_VarEval', 'Tcl_VarEvalVA', 'Tcl_VarTraceInfo',
- 'Tcl_VarTraceInfo2', 'Tcl_VarTraceProc', 'tcl_version', 'Tcl_WaitForEvent', 'Tcl_WaitPid',
- 'Tcl_WinTCharToUtf', 'Tcl_WinUtfToTChar', 'tcl_wordBreakAfter', 'tcl_wordBreakBefore', 'tcl_wordchars',
- 'Tcl_Write', 'Tcl_WriteChars', 'Tcl_WriteObj', 'Tcl_WriteRaw', 'Tcl_WrongNumArgs', 'Tcl_ZlibAdler32',
- 'Tcl_ZlibCRC32', 'Tcl_ZlibDeflate', 'Tcl_ZlibInflate', 'Tcl_ZlibStreamChecksum', 'Tcl_ZlibStreamClose',
- 'Tcl_ZlibStreamEof', 'Tcl_ZlibStreamGet', 'Tcl_ZlibStreamGetCommandName', 'Tcl_ZlibStreamInit',
- 'Tcl_ZlibStreamPut', 'tcltest', 'tell', 'throw', 'time', 'tm', 'trace', 'transchan', 'try', 'unknown',
- 'unload', 'unset', 'update', 'uplevel', 'upvar', 'variable', 'vwait', 'while', 'yield', 'yieldto', 'zlib'
- ]
- self.autocomplete_kw_list = self.defaults['util_autocomplete_keywords'].replace(' ', '').split(',')
- self.myKeywords = self.tcl_commands_list + self.autocomplete_kw_list + self.tcl_keywords
- # ####################################################################################
- # ####################### Shell SETUP ################################################
- # ####################################################################################
- self.shell = FCShell(self, version=self.version)
- self.shell._edit.set_model_data(self.myKeywords)
- self.shell.setWindowIcon(self.ui.app_icon)
- self.shell.setWindowTitle("FlatCAM Shell")
- self.shell.resize(*self.defaults["global_shell_shape"])
- self.shell.append_output("FlatCAM %s - " % self.version)
- self.shell.append_output(_("Open Source Software - Type help to get started\n\n"))
- self.init_tcl()
- self.ui.shell_dock = QtWidgets.QDockWidget("FlatCAM TCL Shell")
- self.ui.shell_dock.setObjectName('Shell_DockWidget')
- self.ui.shell_dock.setWidget(self.shell)
- self.ui.shell_dock.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
- self.ui.shell_dock.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
- QtWidgets.QDockWidget.DockWidgetFloatable |
- QtWidgets.QDockWidget.DockWidgetClosable)
- self.ui.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.ui.shell_dock)
- # show TCL shell at start-up based on the Menu -? Edit -> Preferences setting.
- if self.defaults["global_shell_at_startup"]:
- self.ui.shell_dock.show()
- else:
- self.ui.shell_dock.hide()
- # ##################################################################################
- # ###################### Tools and Plugins #########################################
- # ##################################################################################
- self.dblsidedtool = None
- self.distance_tool = None
- self.distance_min_tool = None
- self.panelize_tool = None
- self.film_tool = None
- self.paste_tool = None
- self.calculator_tool = None
- self.rules_tool = None
- self.sub_tool = None
- self.move_tool = None
- self.cutout_tool = None
- self.ncclear_tool = None
- self.optimal_tool=None
- self.paint_tool = None
- self.transform_tool = None
- self.properties_tool = None
- self.pdf_tool = None
- self.image_tool = None
- self.pcb_wizard_tool = None
- # always install tools only after the shell is initialized because the self.inform.emit() depends on shell
- self.install_tools()
- # ### System Font Parsing ###
- # self.f_parse = ParseFont(self)
- # self.parse_system_fonts()
- # #####################################################################################
- # ######################## Check for updates ##########################################
- # #####################################################################################
- # Separate thread (Not worker)
- # Check for updates on startup but only if the user consent and the app is not in Beta version
- if (self.beta is False or self.beta is None) and \
- self.ui.general_defaults_form.general_gui_group.version_check_cb.get_value() is True:
- App.log.info("Checking for updates in backgroud (this is version %s)." % str(self.version))
- self.thr2 = QtCore.QThread()
- self.worker_task.emit({'fcn': self.version_check,
- 'params': []})
- self.thr2.start(QtCore.QThread.LowPriority)
- # #####################################################################################
- # ######################### Register files with FlatCAM; #############################
- # ######################### It works only for Windows for now ########################
- # #####################################################################################
- if sys.platform == 'win32' and self.defaults["first_run"] is True:
- self.on_register_files()
- # #####################################################################################
- # ###################### Variables for global usage ###################################
- # #####################################################################################
- # coordinates for relative position display
- self.rel_point1 = (0, 0)
- self.rel_point2 = (0, 0)
- # variable to store coordinates
- self.pos = (0, 0)
- self.pos_jump = (0, 0)
- # variable to store mouse coordinates
- self.mouse = [0, 0]
- # decide if we have a double click or single click
- self.doubleclick = False
- # variable to store if a command is active (then the var is not None) and which one it is
- self.command_active = None
- # variable to store the status of moving selection action
- # None value means that it's not an selection action
- # True value = a selection from left to right
- # False value = a selection from right to left
- self.selection_type = None
- # List to store the objects that are currently loaded in FlatCAM
- # This list is updated on each object creation or object delete
- self.all_objects_list = list()
- # List to store the objects that are selected
- self.sel_objects_list = list()
- # holds the key modifier if pressed (CTRL, SHIFT or ALT)
- self.key_modifiers = None
- # Variable to hold the status of the axis
- self.toggle_axis = True
- # Variable to store the status of the fullscreen event
- self.toggle_fscreen = False
- # Variable to store the status of the code editor
- self.toggle_codeeditor = False
- # Variable to be used for situations when we don't want the LMB click on canvas to auto open the Project Tab
- self.click_noproject = False
- self.cursor = None
- # Variable to store the GCODE that was edited
- self.gcode_edited = ""
- # reference for the self.ui.code_editor
- self.reference_code_editor = None
- self.script_code = ''
- # if Preferences are changed in the Edit -> Preferences tab the value will be set to True
- self.preferences_changed_flag = False
- self.grb_list = ['art', 'bot', 'bsm', 'cmp', 'crc', 'crs', 'dim', 'g4', 'gb0', 'gb1', 'gb2', 'gb3', 'gb5',
- 'gb6', 'gb7', 'gb8', 'gb9', 'gbd', 'gbl', 'gbo', 'gbp', 'gbr', 'gbs', 'gdo', 'ger', 'gko',
- 'gml', 'gm1', 'gm2', 'gm3', 'grb', 'gtl', 'gto', 'gtp', 'gts', 'ly15', 'ly2', 'mil', 'pho',
- 'plc', 'pls', 'smb', 'smt', 'sol', 'spb', 'spt', 'ssb', 'sst', 'stc', 'sts', 'top', 'tsm']
- self.exc_list = ['drd', 'drl', 'exc', 'ncd', 'tap', 'txt', 'xln']
- self.gcode_list = ['cnc', 'din', 'dnc', 'ecs', 'eia', 'fan', 'fgc', 'fnc', 'gc', 'gcd', 'gcode', 'h', 'hnc',
- 'i', 'min', 'mpf', 'mpr', 'nc', 'ncc', 'ncg', 'ncp', 'out', 'plt', 'ply', 'rol', 'sbp',
- 'tap', 'xpi']
- self.svg_list = ['svg']
- self.dxf_list = ['dxf']
- self.pdf_list = ['pdf']
- self.prj_list = ['flatprj']
- self.conf_list = ['flatconfig']
- # global variable used by NCC Tool to signal that some polygons could not be cleared, if True
- # flag for polygons not cleared
- self.poly_not_cleared = False
- # VisPy visuals
- self.isHovering = False
- self.notHovering = True
- # Window geometry
- self.x_pos = None
- self.y_pos = None
- self.width = None
- self.height = None
- # Event signals disconnect id holders
- self.mp = None
- self.mm = None
- self.mr = None
- self.mp_zc = None
- # when True, the app has to return from any thread
- self.abort_flag = False
- # set the value used in the Windows Title
- self.engine = self.ui.general_defaults_form.general_app_group.ge_radio.get_value()
- # ###############################################################################
- # ############# Save defaults to factory_defaults.FlatConfig file ###############
- # ############# It's done only once after install ###############
- # ###############################################################################
- factory_file = open(self.data_path + '/factory_defaults.FlatConfig')
- fac_def_from_file = factory_file.read()
- factory_defaults = json.loads(fac_def_from_file)
- # if the file contain an empty dictionary then save the factory defaults into the file
- if not factory_defaults:
- self.save_factory_defaults(silent=False)
- # ONLY AT FIRST STARTUP INIT THE GUI LAYOUT TO 'COMPACT'
- initial_lay = 'compact'
- self.on_layout(lay=initial_lay)
- # Set the combobox in Preferences to the current layout
- idx = self.ui.general_defaults_form.general_gui_set_group.layout_combo.findText(initial_lay)
- self.ui.general_defaults_form.general_gui_set_group.layout_combo.setCurrentIndex(idx)
- factory_file.close()
- # and then make the factory_defaults.FlatConfig file read_only os it can't be modified after creation.
- filename_factory = self.data_path + '/factory_defaults.FlatConfig'
- os.chmod(filename_factory, S_IREAD | S_IRGRP | S_IROTH)
- # after the first run, this object should be False
- self.defaults["first_run"] = False
- # ###############################################################################
- # ################# ADDING FlatCAM EDITORS section ##############################
- # ###############################################################################
- # watch out for the position of the editors instantiation ... if it is done before a save of the default values
- # at the first launch of the App , the editors will not be functional.
- self.geo_editor = FlatCAMGeoEditor(self, disabled=True)
- self.exc_editor = FlatCAMExcEditor(self)
- self.grb_editor = FlatCAMGrbEditor(self)
- self.log.debug("Finished adding FlatCAM Editor's.")
- self.set_ui_title(name=_("New Project - Not saved"))
- # disable the Excellon path optimizations made with Google OR-Tools if the app is run on a 32bit platform
- current_platform = platform.architecture()[0]
- if current_platform != '64bit':
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_optimization_radio.set_value('T')
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_optimization_radio.setDisabled(True)
- # ###############################################################################
- # ####################### Finished the CONSTRUCTOR ##############################
- # ###############################################################################
- App.log.debug("END of constructor. Releasing control.")
- # #####################################################################################
- # ########################## SHOW GUI #################################################
- # #####################################################################################
- # if the app is not started as headless, show it
- if self.cmd_line_headless != 1:
- if show_splash:
- # finish the splash
- self.splash.finish(self.ui)
- settings = QSettings("Open Source", "FlatCAM")
- if settings.contains("maximized_gui"):
- maximized_ui = settings.value('maximized_gui', type=bool)
- if maximized_ui is True:
- self.ui.showMaximized()
- else:
- self.ui.show()
- else:
- self.ui.show()
- if self.defaults["global_systray_icon"]:
- self.trayIcon.show()
- else:
- log.warning("******************* RUNNING HEADLESS *******************")
- # #####################################################################################
- # ########################## START-UP ARGUMENTS #######################################
- # #####################################################################################
- # test if the program was started with a script as parameter
- if self.cmd_line_shellvar:
- try:
- cnt = 0
- command_tcl = 0
- for i in self.cmd_line_shellvar.split(','):
- if i is not None:
- try:
- command_tcl = eval(i)
- except:
- command_tcl = i
- command_tcl_formatted = 'set shellvar_{nr} "{cmd}"'.format(cmd=str(command_tcl), nr=str(cnt))
- cnt += 1
- # if there are Windows paths then replace the path separator with a Unix like one
- if sys.platform == 'win32':
- command_tcl_formatted = command_tcl_formatted.replace('\\', '/')
- self.shell._sysShell.exec_command(command_tcl_formatted, no_echo=True)
- except Exception as ext:
- print("ERROR: ", ext)
- sys.exit(2)
- if self.cmd_line_shellfile:
- try:
- if self.ui.shell_dock.isHidden():
- self.ui.shell_dock.show()
- with open(self.cmd_line_shellfile, "r") as myfile:
- if show_splash:
- self.splash.showMessage('%s: %ssec\n%s' % (
- _("Canvas initialization started.\n"
- "Canvas initialization finished in"), '%.2f' % self.used_time,
- _("Executing Tcl Script ...")),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- cmd_line_shellfile_text = myfile.read()
- self.shell._sysShell.exec_command(cmd_line_shellfile_text)
- except Exception as ext:
- print("ERROR: ", ext)
- sys.exit(2)
- # accept some type file as command line parameter: FlatCAM project, FlatCAM preferences or scripts
- # the path/file_name must be enclosed in quotes if it contain spaces
- if App.args:
- self.args_at_startup.emit(App.args)
- @staticmethod
- def copy_and_overwrite(from_path, to_path):
- """
- From here:
- https://stackoverflow.com/questions/12683834/how-to-copy-directory-recursively-in-python-and-overwrite-all
- :param from_path: source path
- :param to_path: destination path
- :return: None
- """
- if os.path.exists(to_path):
- shutil.rmtree(to_path)
- try:
- shutil.copytree(from_path, to_path)
- except FileNotFoundError:
- from_new_path = os.path.dirname(os.path.realpath(__file__)) + '\\flatcamGUI\\VisPyData\\data'
- shutil.copytree(from_new_path, to_path)
- def on_startup_args(self, args, silent=False):
- """
- This will process any arguments provided to the application at startup. Like trying to launch a file or project.
- :param args: a list containing the application args at startup
- :return: None
- """
- if args is not None:
- args_to_process = args
- else:
- args_to_process = App.args
- log.debug("Application was started with arguments: %s. Processing ..." % str(args_to_process))
- for argument in args_to_process:
- if '.FlatPrj'.lower() in argument.lower():
- try:
- project_name = str(argument)
- if project_name == "":
- if silent is False:
- self.inform.emit(_("Open cancelled."))
- else:
- # self.open_project(project_name)
- run_from_arg = True
- # self.worker_task.emit({'fcn': self.open_project,
- # 'params': [project_name, run_from_arg]})
- self.open_project(filename=project_name, run_from_arg=run_from_arg)
- except Exception as e:
- log.debug("Could not open FlatCAM project file as App parameter due: %s" % str(e))
- elif '.FlatConfig'.lower() in argument.lower():
- try:
- file_name = str(argument)
- if file_name == "":
- if silent is False:
- self.inform.emit(_("Open Config file failed."))
- else:
- run_from_arg = True
- # self.worker_task.emit({'fcn': self.open_config_file,
- # 'params': [file_name, run_from_arg]})
- self.open_config_file(file_name, run_from_arg=run_from_arg)
- except Exception as e:
- log.debug("Could not open FlatCAM Config file as App parameter due: %s" % str(e))
- elif '.FlatScript'.lower() in argument.lower() or '.TCL'.lower() in argument.lower():
- try:
- file_name = str(argument)
- if file_name == "":
- if silent is False:
- self.inform.emit(_("Open Script file failed."))
- else:
- if silent is False:
- self.on_fileopenscript(name=file_name)
- self.ui.plot_tab_area.setCurrentWidget(self.ui.plot_tab)
- self.on_filerunscript(name=file_name)
- except Exception as e:
- log.debug("Could not open FlatCAM Script file as App parameter due: %s" % str(e))
- elif 'quit'.lower() in argument.lower() or 'exit'.lower() in argument.lower():
- log.debug("App.on_startup_args() --> Quit event.")
- sys.exit()
- elif 'save'.lower() in argument.lower():
- log.debug("App.on_startup_args() --> Save event. App Defaults saved.")
- self.save_defaults()
- else:
- exc_list = self.ui.util_defaults_form.fa_excellon_group.exc_list_text.get_value().split(',')
- proc_arg = argument.lower()
- for ext in exc_list:
- proc_ext = ext.replace(' ', '')
- proc_ext = '.%s' % proc_ext
- if proc_ext.lower() in proc_arg and proc_ext != '.':
- file_name = str(argument)
- if file_name == "":
- if silent is False:
- self.inform.emit(_("Open Excellon file failed."))
- else:
- self.on_fileopenexcellon(name=file_name)
- return
- gco_list = self.ui.util_defaults_form.fa_gcode_group.gco_list_text.get_value().split(',')
- for ext in gco_list:
- proc_ext = ext.replace(' ', '')
- proc_ext = '.%s' % proc_ext
- if proc_ext.lower() in proc_arg and proc_ext != '.':
- file_name = str(argument)
- if file_name == "":
- if silent is False:
- self.inform.emit(_("Open GCode file failed."))
- else:
- self.on_fileopengcode(name=file_name)
- return
- grb_list = self.ui.util_defaults_form.fa_gerber_group.grb_list_text.get_value().split(',')
- for ext in grb_list:
- proc_ext = ext.replace(' ', '')
- proc_ext = '.%s' % proc_ext
- if proc_ext.lower() in proc_arg and proc_ext != '.':
- file_name = str(argument)
- if file_name == "":
- if silent is False:
- self.inform.emit(_("Open Gerber file failed."))
- else:
- self.on_fileopengerber(name=file_name)
- return
- # if it reached here without already returning then the app was registered with a file that it does not
- # recognize therefore we must quit but take into consideration the app reboot from within, in that case
- # the args_to_process will contain the path to the FlatCAM.exe (cx_freezed executable)
- # for arg in args_to_process:
- # if 'FlatCAM.exe' in arg:
- # continue
- # else:
- # sys.exit(2)
- def set_ui_title(self, name):
- """
- Sets the title of the main window.
- :param name: String that store the project path and project name
- :return: None
- """
- self.ui.setWindowTitle('FlatCAM %s %s - %s - [%s] %s' %
- (self.version,
- ('BETA' if self.beta else ''),
- platform.architecture()[0],
- self.engine,
- name)
- )
- def on_app_restart(self):
- # make sure that the Sys Tray icon is hidden before restart otherwise it will
- # be left in the SySTray
- try:
- self.trayIcon.hide()
- except Exception as e:
- log.debug("App.on_app_restart() --> %s" % str(e))
- fcTranslate.restart_program(app=self)
- def defaults_read_form(self):
- """
- Will read all the values in the Preferences GUI and update the defaults dictionary.
- :return: None
- """
- for option in self.defaults_form_fields:
- try:
- self.defaults[option] = self.defaults_form_fields[option].get_value()
- except Exception as e:
- log.debug("App.defaults_read_form() --> %s" % str(e))
- def defaults_write_form(self, factor=None, fl_units=None):
- """
- Will set the values for all the GUI elements in Preferences GUI based on the values found in the
- self.defaults dictionary.
- :param factor: will apply a factor to the values that written in the GUI elements
- :param fl_units: current measuring units in FlatCAM: Metric or Inch
- :return: None
- """
- for option in self.defaults:
- self.defaults_write_form_field(option, factor=factor, units=fl_units)
- # try:
- # self.defaults_form_fields[option].set_value(self.defaults[option])
- # except KeyError:
- # #self.log.debug("defaults_write_form(): No field for: %s" % option)
- # # TODO: Rethink this?
- # pass
- def defaults_write_form_field(self, field, factor=None, units=None):
- """
- Basically it is the worker in the self.defaults_write_form()
- :param field: the GUI element in Preferences GUI to be updated
- :param factor: factor to be applied to the field parameter
- :param units: current FLatCAM measuring units
- :return: None, it updates GUI elements
- """
- try:
- if factor is None:
- if units is None:
- self.defaults_form_fields[field].set_value(self.defaults[field])
- elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'):
- self.defaults_form_fields[field].set_value(self.defaults[field], decimals=6)
- elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'):
- self.defaults_form_fields[field].set_value(self.defaults[field], decimals=4)
- else:
- if units is None:
- self.defaults_form_fields[field].set_value(self.defaults[field] * factor)
- elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'):
- self.defaults_form_fields[field].set_value((self.defaults[field] * factor), decimals=6)
- elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'):
- self.defaults_form_fields[field].set_value((self.defaults[field] * factor), decimals=4)
- except KeyError:
- # self.log.debug("defaults_write_form(): No field for: %s" % option)
- # TODO: Rethink this?
- pass
- except AttributeError:
- log.debug(field)
- def clear_pool(self):
- """
- Clear the multiprocessing pool and calls garbage collector.
- :return: None
- """
- self.pool.close()
- self.pool = Pool()
- self.pool_recreated.emit(self.pool)
- gc.collect()
- def install_tools(self):
- """
- This installs the FlatCAM tools (plugin-like) which reside in their own classes.
- Instantiation of the Tools classes.
- The order that the tools are installed is important as they can depend on each other install position.
- :return: None
- """
- self.dblsidedtool = DblSidedTool(self)
- self.dblsidedtool.install(icon=QtGui.QIcon('share/doubleside16.png'), separator=True)
- self.distance_tool = Distance(self)
- self.distance_tool.install(icon=QtGui.QIcon('share/distance16.png'), pos=self.ui.menuedit,
- before=self.ui.menueditorigin,
- separator=False)
- self.distance_min_tool = DistanceMin(self)
- self.distance_min_tool.install(icon=QtGui.QIcon('share/distance_min16.png'), pos=self.ui.menuedit,
- before=self.ui.menueditorigin,
- separator=True)
- self.panelize_tool = Panelize(self)
- self.panelize_tool.install(icon=QtGui.QIcon('share/panel16.png'))
- self.film_tool = Film(self)
- self.film_tool.install(icon=QtGui.QIcon('share/film16.png'))
- self.paste_tool = SolderPaste(self)
- self.paste_tool.install(icon=QtGui.QIcon('share/solderpastebis32.png'))
- self.calculator_tool = ToolCalculator(self)
- self.calculator_tool.install(icon=QtGui.QIcon('share/calculator16.png'), separator=True)
- self.sub_tool = ToolSub(self)
- self.sub_tool.install(icon=QtGui.QIcon('share/sub32.png'), pos=self.ui.menutool, separator=True)
- self.rules_tool = RulesCheck(self)
- self.rules_tool.install(icon=QtGui.QIcon('share/rules32.png'), pos=self.ui.menutool, separator=False)
- self.optimal_tool = ToolOptimal(self)
- self.optimal_tool.install(icon=QtGui.QIcon('share/open_excellon32.png'), pos=self.ui.menutool, separator=True)
- self.move_tool = ToolMove(self)
- self.move_tool.install(icon=QtGui.QIcon('share/move16.png'), pos=self.ui.menuedit,
- before=self.ui.menueditorigin, separator=True)
- self.cutout_tool = CutOut(self)
- self.cutout_tool.install(icon=QtGui.QIcon('share/cut16_bis.png'), pos=self.ui.menutool,
- before=self.sub_tool.menuAction)
- self.ncclear_tool = NonCopperClear(self)
- self.ncclear_tool.install(icon=QtGui.QIcon('share/ncc16.png'), pos=self.ui.menutool,
- before=self.sub_tool.menuAction, separator=True)
- self.paint_tool = ToolPaint(self)
- self.paint_tool.install(icon=QtGui.QIcon('share/paint16.png'), pos=self.ui.menutool,
- before=self.sub_tool.menuAction, separator=True)
- self.transform_tool = ToolTransform(self)
- self.transform_tool.install(icon=QtGui.QIcon('share/transform.png'), pos=self.ui.menuoptions, separator=True)
- self.properties_tool = Properties(self)
- self.properties_tool.install(icon=QtGui.QIcon('share/properties32.png'), pos=self.ui.menuoptions)
- self.pdf_tool = ToolPDF(self)
- self.pdf_tool.install(icon=QtGui.QIcon('share/pdf32.png'), pos=self.ui.menufileimport,
- separator=True)
- self.image_tool = ToolImage(self)
- self.image_tool.install(icon=QtGui.QIcon('share/image32.png'), pos=self.ui.menufileimport,
- separator=True)
- self.pcb_wizard_tool = PcbWizard(self)
- self.pcb_wizard_tool.install(icon=QtGui.QIcon('share/drill32.png'), pos=self.ui.menufileimport)
- self.log.debug("Tools are installed.")
- def remove_tools(self):
- """
- Will remove all the actions in the Tool menu.
- :return: None
- """
- for act in self.ui.menutool.actions():
- self.ui.menutool.removeAction(act)
- def init_tools(self):
- """
- Initialize the Tool tab in the notebook side of the central widget.
- Remove the actions in the Tools menu.
- Instantiate again the FlatCAM tools (plugins).
- All this is required when changing the layout: standard, compact etc.
- :return: None
- """
- log.debug("init_tools()")
- # delete the data currently in the Tools Tab and the Tab itself
- widget = QtWidgets.QTabWidget.widget(self.ui.notebook, 2)
- if widget is not None:
- widget.deleteLater()
- self.ui.notebook.removeTab(2)
- # rebuild the Tools Tab
- self.ui.tool_tab = QtWidgets.QWidget()
- self.ui.tool_tab_layout = QtWidgets.QVBoxLayout(self.ui.tool_tab)
- self.ui.tool_tab_layout.setContentsMargins(2, 2, 2, 2)
- self.ui.notebook.addTab(self.ui.tool_tab, "Tool")
- self.ui.tool_scroll_area = VerticalScrollArea()
- self.ui.tool_tab_layout.addWidget(self.ui.tool_scroll_area)
- # reinstall all the Tools as some may have been removed when the data was removed from the Tools Tab
- # first remove all of them
- self.remove_tools()
- # re-add the TCL Shell action to the Tools menu and reconnect it to ist slot function
- self.ui.menutoolshell = self.ui.menutool.addAction(QtGui.QIcon('share/shell16.png'), '&Command Line\tS')
- self.ui.menutoolshell.triggered.connect(self.on_toggle_shell)
- # third install all of them
- self.install_tools()
- self.log.debug("Tools are initialized.")
- # def parse_system_fonts(self):
- # self.worker_task.emit({'fcn': self.f_parse.get_fonts_by_types,
- # 'params': []})
- def connect_toolbar_signals(self):
- """
- Reconnect the signals to the actions in the toolbar.
- This has to be done each time after the FlatCAM tools are removed/installed.
- :return: None
- """
- # Toolbar
- # self.ui.file_new_btn.triggered.connect(self.on_file_new)
- self.ui.file_open_btn.triggered.connect(self.on_file_openproject)
- self.ui.file_save_btn.triggered.connect(self.on_file_saveproject)
- self.ui.file_open_gerber_btn.triggered.connect(self.on_fileopengerber)
- self.ui.file_open_excellon_btn.triggered.connect(self.on_fileopenexcellon)
- self.ui.clear_plot_btn.triggered.connect(self.clear_plots)
- self.ui.replot_btn.triggered.connect(self.plot_all)
- self.ui.zoom_fit_btn.triggered.connect(self.on_zoom_fit)
- self.ui.zoom_in_btn.triggered.connect(lambda: self.plotcanvas.zoom(1 / 1.5))
- self.ui.zoom_out_btn.triggered.connect(lambda: self.plotcanvas.zoom(1.5))
- self.ui.newgeo_btn.triggered.connect(self.new_geometry_object)
- self.ui.newgrb_btn.triggered.connect(self.new_gerber_object)
- self.ui.newexc_btn.triggered.connect(self.new_excellon_object)
- self.ui.editgeo_btn.triggered.connect(self.object2editor)
- self.ui.update_obj_btn.triggered.connect(lambda: self.editor2object())
- self.ui.delete_btn.triggered.connect(self.on_delete)
- self.ui.distance_btn.triggered.connect(lambda: self.distance_tool.run(toggle=True))
- self.ui.distance_min_btn.triggered.connect(lambda: self.distance_min_tool.run(toggle=True))
- self.ui.origin_btn.triggered.connect(self.on_set_origin)
- self.ui.jmp_btn.triggered.connect(self.on_jump_to)
- self.ui.shell_btn.triggered.connect(self.on_toggle_shell)
- self.ui.new_script_btn.triggered.connect(self.on_filenewscript)
- self.ui.open_script_btn.triggered.connect(self.on_fileopenscript)
- self.ui.run_script_btn.triggered.connect(self.on_filerunscript)
- # Tools Toolbar Signals
- self.ui.dblsided_btn.triggered.connect(lambda: self.dblsidedtool.run(toggle=True))
- self.ui.cutout_btn.triggered.connect(lambda: self.cutout_tool.run(toggle=True))
- self.ui.ncc_btn.triggered.connect(lambda: self.ncclear_tool.run(toggle=True))
- self.ui.paint_btn.triggered.connect(lambda: self.paint_tool.run(toggle=True))
- self.ui.panelize_btn.triggered.connect(lambda: self.panelize_tool.run(toggle=True))
- self.ui.film_btn.triggered.connect(lambda: self.film_tool.run(toggle=True))
- self.ui.solder_btn.triggered.connect(lambda: self.paste_tool.run(toggle=True))
- self.ui.sub_btn.triggered.connect(lambda: self.sub_tool.run(toggle=True))
- self.ui.rules_btn.triggered.connect(lambda: self.rules_tool.run(toggle=True))
- self.ui.optimal_btn.triggered.connect(lambda: self.optimal_tool.run(toggle=True))
- self.ui.calculators_btn.triggered.connect(lambda: self.calculator_tool.run(toggle=True))
- self.ui.transform_btn.triggered.connect(lambda: self.transform_tool.run(toggle=True))
- def object2editor(self):
- """
- Send the current Geometry or Excellon object (if any) into the it's editor.
- :return: None
- """
- self.report_usage("object2editor()")
- edited_object = self.collection.get_active()
- if isinstance(edited_object, FlatCAMGerber) or isinstance(edited_object, FlatCAMGeometry) or \
- isinstance(edited_object, FlatCAMExcellon):
- pass
- else:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Select a Geometry, Gerber or Excellon Object to edit."))
- return
- if isinstance(edited_object, FlatCAMGeometry):
- # store the Geometry Editor Toolbar visibility before entering in the Editor
- self.geo_editor.toolbar_old_state = True if self.ui.geo_edit_toolbar.isVisible() else False
- # we set the notebook to hidden
- self.ui.splitter.setSizes([0, 1])
- if edited_object.multigeo is True:
- edited_tools = [int(x.text()) for x in edited_object.ui.geo_tools_table.selectedItems()]
- if len(edited_tools) > 1:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Simultanoeus editing of tools geometry in a MultiGeo Geometry "
- "is not possible.\n"
- "Edit only one geometry at a time."))
- # determine the tool dia of the selected tool
- selected_tooldia = float(edited_object.ui.geo_tools_table.item((edited_tools[0] - 1), 1).text())
- # now find the key in the edited_object.tools that has this tooldia
- multi_tool = 1
- for tool in edited_object.tools:
- if edited_object.tools[tool]['tooldia'] == selected_tooldia:
- multi_tool = tool
- break
- self.geo_editor.edit_fcgeometry(edited_object, multigeo_tool=multi_tool)
- else:
- self.geo_editor.edit_fcgeometry(edited_object)
- # set call source to the Editor we go into
- self.call_source = 'geo_editor'
- elif isinstance(edited_object, FlatCAMExcellon):
- # store the Excellon Editor Toolbar visibility before entering in the Editor
- self.exc_editor.toolbar_old_state = True if self.ui.exc_edit_toolbar.isVisible() else False
- if self.ui.splitter.sizes()[0] == 0:
- self.ui.splitter.setSizes([1, 1])
- self.exc_editor.edit_fcexcellon(edited_object)
- # set call source to the Editor we go into
- self.call_source = 'exc_editor'
- elif isinstance(edited_object, FlatCAMGerber):
- # store the Gerber Editor Toolbar visibility before entering in the Editor
- self.grb_editor.toolbar_old_state = True if self.ui.grb_edit_toolbar.isVisible() else False
- if self.ui.splitter.sizes()[0] == 0:
- self.ui.splitter.setSizes([1, 1])
- self.grb_editor.edit_fcgerber(edited_object)
- # set call source to the Editor we go into
- self.call_source = 'grb_editor'
- # make sure that we can't select another object while in Editor Mode:
- # self.collection.view.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
- self.ui.project_frame.setDisabled(True)
- # delete any selection shape that might be active as they are not relevant in Editor
- self.delete_selection_shape()
- self.ui.plot_tab_area.setTabText(0, "EDITOR Area")
- self.ui.plot_tab_area.protectTab(0)
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Editor is activated ..."))
- self.should_we_save = True
- def editor2object(self, cleanup=None):
- """
- Transfers the Geometry or Excellon from it's editor to the current object.
- :return: None
- """
- self.report_usage("editor2object()")
- # do not update a geometry or excellon object unless it comes out of an editor
- if self.call_source != 'app':
- edited_obj = self.collection.get_active()
- if cleanup is None:
- msgbox = QtWidgets.QMessageBox()
- msgbox.setText(_("Do you want to save the edited object?"))
- msgbox.setWindowTitle(_("Close Editor"))
- msgbox.setWindowIcon(QtGui.QIcon('share/save_as.png'))
- bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
- bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
- bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.RejectRole)
- msgbox.setDefaultButton(bt_yes)
- msgbox.exec_()
- response = msgbox.clickedButton()
- if response == bt_yes:
- # clean the Tools Tab
- self.ui.tool_scroll_area.takeWidget()
- self.ui.tool_scroll_area.setWidget(QtWidgets.QWidget())
- self.ui.notebook.setTabText(2, "Tool")
- if isinstance(edited_obj, FlatCAMGeometry):
- obj_type = "Geometry"
- if cleanup is None:
- self.geo_editor.update_fcgeometry(edited_obj)
- self.geo_editor.update_options(edited_obj)
- self.geo_editor.deactivate()
- # update the geo object options so it is including the bounding box values
- try:
- xmin, ymin, xmax, ymax = edited_obj.bounds()
- edited_obj.options['xmin'] = xmin
- edited_obj.options['ymin'] = ymin
- edited_obj.options['xmax'] = xmax
- edited_obj.options['ymax'] = ymax
- except AttributeError as e:
- self.inform.emit('[WARNING] %s' %
- _("Object empty after edit."))
- log.debug("App.editor2object() --> Geometry --> %s" % str(e))
- elif isinstance(edited_obj, FlatCAMGerber):
- obj_type = "Gerber"
- if cleanup is None:
- self.grb_editor.update_fcgerber()
- self.grb_editor.update_options(edited_obj)
- self.grb_editor.deactivate_grb_editor()
- # delete the old object (the source object) if it was an empty one
- try:
- if len(edited_obj.solid_geometry) == 0:
- old_name = edited_obj.options['name']
- self.collection.set_active(old_name)
- self.collection.delete_active()
- except TypeError:
- # if the solid_geometry is a single Polygon the len() will not work
- # in any case, falling here means that we have something in the solid_geometry, even if only
- # a single Polygon, therefore we pass this
- pass
- # restore GUI to the Selected TAB
- # Remove anything else in the GUI
- self.ui.selected_scroll_area.takeWidget()
- # Switch notebook to Selected page
- self.ui.notebook.setCurrentWidget(self.ui.selected_tab)
- elif isinstance(edited_obj, FlatCAMExcellon):
- obj_type = "Excellon"
- if cleanup is None:
- self.exc_editor.update_fcexcellon(edited_obj)
- self.exc_editor.update_options(edited_obj)
- self.exc_editor.deactivate()
- # delete the old object (the source object) if it was an empty one
- if len(edited_obj.drills) == 0 and len(edited_obj.slots) == 0:
- old_name = edited_obj.options['name']
- self.collection.set_active(old_name)
- self.collection.delete_active()
- # restore GUI to the Selected TAB
- # Remove anything else in the GUI
- self.ui.tool_scroll_area.takeWidget()
- # Switch notebook to Selected page
- self.ui.notebook.setCurrentWidget(self.ui.selected_tab)
- else:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Select a Gerber, Geometry or Excellon Object to update."))
- return
- self.inform.emit('[selected] %s %s' %
- (obj_type, _("is updated, returning to App...")))
- elif response == bt_no:
- # clean the Tools Tab
- self.ui.tool_scroll_area.takeWidget()
- self.ui.tool_scroll_area.setWidget(QtWidgets.QWidget())
- self.ui.notebook.setTabText(2, "Tool")
- if isinstance(edited_obj, FlatCAMGeometry):
- self.geo_editor.deactivate()
- elif isinstance(edited_obj, FlatCAMGerber):
- self.grb_editor.deactivate_grb_editor()
- elif isinstance(edited_obj, FlatCAMExcellon):
- self.exc_editor.deactivate()
- # set focus on the project tab
- else:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Select a Gerber, Geometry or Excellon Object to update."))
- return
- edited_obj.set_ui(edited_obj.ui_type())
- self.ui.notebook.setCurrentWidget(self.ui.selected_tab)
- elif response == bt_cancel:
- return
- else:
- if isinstance(edited_obj, FlatCAMGeometry):
- self.geo_editor.deactivate()
- elif isinstance(edited_obj, FlatCAMGerber):
- self.grb_editor.deactivate_grb_editor()
- elif isinstance(edited_obj, FlatCAMExcellon):
- self.exc_editor.deactivate()
- else:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Select a Gerber, Geometry or Excellon Object to update."))
- return
- # if notebook is hidden we show it
- if self.ui.splitter.sizes()[0] == 0:
- self.ui.splitter.setSizes([1, 1])
- # restore the call_source to app
- self.call_source = 'app'
- edited_obj.plot()
- self.ui.plot_tab_area.setTabText(0, "Plot Area")
- self.ui.plot_tab_area.protectTab(0)
- # make sure that we reenable the selection on Project Tab after returning from Editor Mode:
- # self.collection.view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
- self.ui.project_frame.setDisabled(False)
- def get_last_folder(self):
- """
- Get the folder path from where the last file was opened.
- :return: String, last opened folder path
- """
- return self.defaults["global_last_folder"]
- def get_last_save_folder(self):
- """
- Get the folder path from where the last file was saved.
- :return: String, last saved folder path
- """
- loc = self.defaults["global_last_save_folder"]
- if loc is None:
- loc = self.defaults["global_last_folder"]
- if loc is None:
- loc = os.path.dirname(__file__)
- return loc
- def report_usage(self, resource):
- """
- Increments usage counter for the given resource
- in self.defaults['global_stats'].
- :param resource: Name of the resource.
- :return: None
- """
- if resource in self.defaults['global_stats']:
- self.defaults['global_stats'][resource] += 1
- else:
- self.defaults['global_stats'][resource] = 1
- def init_tcl(self):
- """
- Initialize the TCL Shell. A dock widget that holds the GUI interface to the FlatCAM command line.
- :return: None
- """
- if hasattr(self, 'tcl'):
- # self.tcl = None
- # TODO we need to clean non default variables and procedures here
- # new object cannot be used here as it will not remember values created for next passes,
- # because tcl was execudted in old instance of TCL
- pass
- else:
- self.tcl = tk.Tcl()
- self.setup_shell()
- self.log.debug("TCL Shell has been initialized.")
- # TODO: This shouldn't be here.
- class TclErrorException(Exception):
- """
- this exception is defined here, to be able catch it if we successfully handle all errors from shell command
- """
- pass
- def shell_message(self, msg, show=False, error=False, warning=False, success=False, selected=False):
- """
- Shows a message on the FlatCAM Shell
- :param msg: Message to display.
- :param show: Opens the shell.
- :param error: Shows the message as an error.
- :param warning: Shows the message as an warning.
- :param success: Shows the message as an success.
- :param selected: Indicate that something was selected on canvas
- :return: None
- """
- if show:
- self.ui.shell_dock.show()
- try:
- if error:
- self.shell.append_error(msg + "\n")
- elif warning:
- self.shell.append_warning(msg + "\n")
- elif success:
- self.shell.append_success(msg + "\n")
- elif selected:
- self.shell.append_selected(msg + "\n")
- else:
- self.shell.append_output(msg + "\n")
- except AttributeError:
- log.debug("shell_message() is called before Shell Class is instantiated. The message is: %s", str(msg))
- def raise_tcl_unknown_error(self, unknownException):
- """
- Raise exception if is different type than TclErrorException
- this is here mainly to show unknown errors inside TCL shell console.
- :param unknownException:
- :return:
- """
- if not isinstance(unknownException, self.TclErrorException):
- self.raise_tcl_error("Unknown error: %s" % str(unknownException))
- else:
- raise unknownException
- def display_tcl_error(self, error, error_info=None):
- """
- Escape bracket [ with '\' otherwise there is error
- "ERROR: missing close-bracket" instead of real error
- :param error: it may be text or exception
- :param error_info: Some informations about the error
- :return: None
- """
- if isinstance(error, Exception):
- exc_type, exc_value, exc_traceback = error_info
- if not isinstance(error, self.TclErrorException):
- show_trace = 1
- else:
- show_trace = int(self.defaults['global_verbose_error_level'])
- if show_trace > 0:
- trc = traceback.format_list(traceback.extract_tb(exc_traceback))
- trc_formated = []
- for a in reversed(trc):
- trc_formated.append(a.replace(" ", " > ").replace("\n", ""))
- text = "%s\nPython traceback: %s\n%s" % (exc_value, exc_type, "\n".join(trc_formated))
- else:
- text = "%s" % error
- else:
- text = error
- text = text.replace('[', '\\[').replace('"', '\\"')
- self.tcl.eval('return -code error "%s"' % text)
- def raise_tcl_error(self, text):
- """
- This method pass exception from python into TCL as error, so we get stacktrace and reason
- :param text: text of error
- :return: raise exception
- """
- self.display_tcl_error(text)
- raise self.TclErrorException(text)
- def exec_command(self, text, no_echo=False):
- """
- Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
- Also handles execution in separated threads
- :param text: FlatCAM TclCommand with parameters
- :param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
- will create crashes of the _Expandable_Edit widget
- :return: output if there was any
- """
- self.report_usage('exec_command')
- result = self.exec_command_test(text, False, no_echo=no_echo)
- # MS: added this method call so the geometry is updated once the TCL command is executed
- # if no_plot is None:
- # self.plot_all()
- return result
- def exec_command_test(self, text, reraise=True, no_echo=False):
- """
- Same as exec_command(...) with additional control over exceptions.
- Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
- :param text: Input command
- :param reraise: Re-raise TclError exceptions in Python (mostly for unitttests).
- :param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
- will create crashes of the _Expandable_Edit widget
- :return: Output from the command
- """
- tcl_command_string = str(text)
- try:
- if no_echo is False:
- self.shell.open_proccessing() # Disables input box.
- result = self.tcl.eval(str(tcl_command_string))
- if result != 'None' and no_echo is False:
- self.shell.append_output(result + '\n')
- except tk.TclError as e:
- # This will display more precise answer if something in TCL shell fails
- result = self.tcl.eval("set errorInfo")
- self.log.error("Exec command Exception: %s" % (result + '\n'))
- if no_echo is False:
- self.shell.append_error('ERROR: ' + result + '\n')
- # Show error in console and just return or in test raise exception
- if reraise:
- raise e
- finally:
- if no_echo is False:
- self.shell.close_proccessing()
- pass
- return result
- # """
- # Code below is unsused. Saved for later.
- # """
- # parts = re.findall(r'([\w\\:\.]+|".*?")+', text)
- # parts = [p.replace('\n', '').replace('"', '') for p in parts]
- # self.log.debug(parts)
- # try:
- # if parts[0] not in commands:
- # self.shell.append_error("Unknown command\n")
- # return
- #
- # #import inspect
- # #inspect.getargspec(someMethod)
- # if (type(commands[parts[0]]["params"]) is not list and len(parts)-1 != commands[parts[0]]["params"]) or \
- # (type(commands[parts[0]]["params"]) is list and len(parts)-1 not in commands[parts[0]]["params"]):
- # self.shell.append_error(
- # "Command %s takes %d arguments. %d given.\n" %
- # (parts[0], commands[parts[0]]["params"], len(parts)-1)
- # )
- # return
- #
- # cmdfcn = commands[parts[0]]["fcn"]
- # cmdconv = commands[parts[0]]["converters"]
- # if len(parts) - 1 > 0:
- # retval = cmdfcn(*[cmdconv[i](parts[i + 1]) for i in range(len(parts)-1)])
- # else:
- # retval = cmdfcn()
- # retfcn = commands[parts[0]]["retfcn"]
- # if retval and retfcn(retval):
- # self.shell.append_output(retfcn(retval) + "\n")
- #
- # except Exception as e:
- # #self.shell.append_error(''.join(traceback.format_exc()))
- # #self.shell.append_error("?\n")
- # self.shell.append_error(str(e) + "\n")
- def info(self, msg):
- """
- Informs the user. Normally on the status bar, optionally
- also on the shell.
- :param msg: Text to write.
- :return: None
- """
- # Type of message in brackets at the beginning of the message.
- match = re.search("\[([^\]]+)\](.*)", msg)
- if match:
- level = match.group(1)
- msg_ = match.group(2)
- self.ui.fcinfo.set_status(str(msg_), level=level)
- if level.lower() == "error":
- self.shell_message(msg, error=True, show=True)
- elif level.lower() == "warning":
- self.shell_message(msg, warning=True, show=True)
- elif level.lower() == "error_notcl":
- self.shell_message(msg, error=True, show=False)
- elif level.lower() == "warning_notcl":
- self.shell_message(msg, warning=True, show=False)
- elif level.lower() == "success":
- self.shell_message(msg, success=True, show=False)
- elif level.lower() == "selected":
- self.shell_message(msg, selected=True, show=False)
- else:
- self.shell_message(msg, show=False)
- else:
- self.ui.fcinfo.set_status(str(msg), level="info")
- # make sure that if the message is to clear the infobar with a space
- # is not printed over and over on the shell
- if msg != '':
- self.shell_message(msg)
- def restore_toolbar_view(self):
- """
- Some toolbars may be hidden by user and here we restore the state of the toolbars visibility that
- was saved in the defaults dictionary.
- :return: None
- """
- tb = self.defaults["global_toolbar_view"]
- if tb & 1:
- self.ui.toolbarfile.setVisible(True)
- else:
- self.ui.toolbarfile.setVisible(False)
- if tb & 2:
- self.ui.toolbargeo.setVisible(True)
- else:
- self.ui.toolbargeo.setVisible(False)
- if tb & 4:
- self.ui.toolbarview.setVisible(True)
- else:
- self.ui.toolbarview.setVisible(False)
- if tb & 8:
- self.ui.toolbartools.setVisible(True)
- else:
- self.ui.toolbartools.setVisible(False)
- if tb & 16:
- self.ui.exc_edit_toolbar.setVisible(True)
- else:
- self.ui.exc_edit_toolbar.setVisible(False)
- if tb & 32:
- self.ui.geo_edit_toolbar.setVisible(True)
- else:
- self.ui.geo_edit_toolbar.setVisible(False)
- if tb & 64:
- self.ui.grb_edit_toolbar.setVisible(True)
- else:
- self.ui.grb_edit_toolbar.setVisible(False)
- if tb & 128:
- self.ui.snap_toolbar.setVisible(True)
- else:
- self.ui.snap_toolbar.setVisible(False)
- if tb & 256:
- self.ui.toolbarshell.setVisible(True)
- else:
- self.ui.toolbarshell.setVisible(False)
- def load_defaults(self, filename):
- """
- Loads the aplication's default settings from current_defaults.FlatConfig into
- ``self.defaults``.
- :return: None
- """
- try:
- f = open(self.data_path + "/" + filename + ".FlatConfig")
- options = f.read()
- f.close()
- except IOError:
- self.log.error("Could not load defaults file.")
- self.inform.emit('[ERROR] %s' %
- _("Could not load defaults file."))
- # in case the defaults file can't be loaded, show all toolbars
- self.defaults["global_toolbar_view"] = 511
- return
- try:
- defaults = json.loads(options)
- except:
- # in case the defaults file can't be loaded, show all toolbars
- self.defaults["global_toolbar_view"] = 511
- e = sys.exc_info()[0]
- App.log.error(str(e))
- self.inform.emit('[ERROR] %s' %
- _("Failed to parse defaults file."))
- return
- self.defaults.update(defaults)
- log.debug("FlatCAM defaults loaded from: %s" % filename)
- # restore the toolbar view
- self.restore_toolbar_view()
- def on_import_preferences(self):
- """
- Loads the aplication's factory default settings from factory_defaults.FlatConfig into
- ``self.defaults``.
- :return: None
- """
- self.report_usage("on_import_preferences")
- App.log.debug("on_import_preferences()")
- filter_ = "Config File (*.FlatConfig);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Preferences"),
- directory=self.data_path,
- filter=filter_)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Preferences"),
- filter=filter_)
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("FlatCAM preferences import cancelled."))
- else:
- try:
- f = open(filename)
- options = f.read()
- f.close()
- except IOError:
- self.log.error("Could not load defaults file.")
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Could not load defaults file."))
- return
- try:
- defaults_from_file = json.loads(options)
- except Exception as e:
- e = sys.exc_info()[0]
- App.log.error(str(e))
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to parse defaults file."))
- return
- self.defaults.update(defaults_from_file)
- self.on_preferences_edited()
- self.inform.emit('[success] %s: %s' %
- (_("Imported Defaults from"), filename))
- def on_export_preferences(self):
- """
- Save the defaults dictionary to a file.
- :return: None
- """
- self.report_usage("on_export_preferences")
- App.log.debug("on_export_preferences()")
- defaults_file_content = None
- self.date = str(datetime.today()).rpartition('.')[0]
- self.date = ''.join(c for c in self.date if c not in ':-')
- self.date = self.date.replace(' ', '_')
- filter__ = "Config File (*.FlatConfig);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption=_("Export FlatCAM Preferences"),
- directory=self.data_path + '/preferences_' + self.date,
- filter=filter__
- )
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export FlatCAM Preferences"),
- filter=filter__)
- filename = str(filename)
- defaults_from_file = {}
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("FlatCAM preferences export cancelled."))
- return
- else:
- try:
- f = open(filename, 'w')
- defaults_file_content = f.read()
- f.close()
- except PermissionError:
- self.inform.emit('[WARNING] %s' %
- _("Permission denied, saving not possible.\n"
- "Most likely another app is holding the file open and not accessible."))
- return
- except IOError:
- App.log.debug('Creating a new preferences file ...')
- f = open(filename, 'w')
- json.dump({}, f)
- f.close()
- except:
- e = sys.exc_info()[0]
- App.log.error("Could not load defaults file.")
- App.log.error(str(e))
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Could not load preferences file."))
- return
- try:
- defaults_from_file = json.loads(defaults_file_content)
- except:
- App.log.warning("Trying to read an empty Preferences file. Continue.")
- # Update options
- self.defaults_read_form()
- defaults_from_file.update(self.defaults)
- self.propagate_defaults(silent=True)
- # Save update options
- try:
- f = open(filename, "w")
- json.dump(defaults_from_file, f, default=to_dict, indent=2, sort_keys=True)
- f.close()
- except:
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to write defaults to file."))
- return
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("preferences", filename)
- self.file_saved.emit("preferences", filename)
- self.inform.emit('[success] %s: %s' %
- (_("Exported preferences to"), filename))
- def on_preferences_open_folder(self):
- """
- Will open an Explorer window set to the folder path where the FlatCAM preferences files are usually saved.
- :return: None
- """
- self.report_usage("on_preferences_open_folder()")
- if sys.platform == 'win32':
- subprocess.Popen('explorer %s' % self.data_path)
- elif sys.platform == 'darwin':
- os.system('open "%s"' % self.data_path)
- else:
- subprocess.Popen(['xdg-open', self.data_path])
- self.inform.emit('[success] %s' %
- _("FlatCAM Preferences Folder opened."))
- def save_geometry(self, x, y, width, height, notebook_width):
- """
- Will save the application geometry and positions in the defaults discitionary to be restored at the next
- launch of the application.
- :param x: X position of the main window
- :param y: Y position of the main window
- :param width: width of the main window
- :param height: height of the main window
- :param notebook_width: the notebook width is adjustable so it get saved here, too.
- :return: None
- """
- self.defaults["global_def_win_x"] = x
- self.defaults["global_def_win_y"] = y
- self.defaults["global_def_win_w"] = width
- self.defaults["global_def_win_h"] = height
- self.defaults["global_def_notebook_width"] = notebook_width
- self.save_defaults()
- def restore_main_win_geom(self):
- try:
- self.ui.setGeometry(self.defaults["global_def_win_x"],
- self.defaults["global_def_win_y"],
- self.defaults["global_def_win_w"],
- self.defaults["global_def_win_h"])
- self.ui.splitter.setSizes([self.defaults["global_def_notebook_width"], 0])
- except KeyError as e:
- log.debug("App.restore_main_win_geom() --> %s" % str(e))
- def message_dialog(self, title, message, kind="info"):
- """
- Builds and show a custom QMessageBox to be used in FlatCAM.
- :param title: title of the QMessageBox
- :param message: message to be displayed
- :param kind: type of QMessageBox; will display a specific icon.
- :return:
- """
- icon = {"info": QtWidgets.QMessageBox.Information,
- "warning": QtWidgets.QMessageBox.Warning,
- "error": QtWidgets.QMessageBox.Critical}[str(kind)]
- dlg = QtWidgets.QMessageBox(icon, title, message, parent=self.ui)
- dlg.setText(message)
- dlg.exec_()
- def register_recent(self, kind, filename):
- """
- Will register the files opened into record dictionaries. The FlatCAM projects has it's own
- dictionary.
- :param kind: type of file that was opened
- :param filename: the path and file name for the file that was opened
- :return:
- """
- self.log.debug("register_recent()")
- self.log.debug(" %s" % kind)
- self.log.debug(" %s" % filename)
- record = {'kind': str(kind), 'filename': str(filename)}
- if record in self.recent:
- return
- if record in self.recent_projects:
- return
- if record['kind'] == 'project':
- self.recent_projects.insert(0, record)
- else:
- self.recent.insert(0, record)
- if len(self.recent) > self.defaults['global_recent_limit']: # Limit reached
- self.recent.pop()
- if len(self.recent_projects) > self.defaults['global_recent_limit']: # Limit reached
- self.recent_projects.pop()
- try:
- f = open(self.data_path + '/recent.json', 'w')
- except IOError:
- App.log.error("Failed to open recent items file for writing.")
- self.inform.emit('[ERROR_NOTCL] %s' %
- _('Failed to open recent files file for writing.'))
- return
- json.dump(self.recent, f, default=to_dict, indent=2, sort_keys=True)
- f.close()
- try:
- fp = open(self.data_path + '/recent_projects.json', 'w')
- except IOError:
- App.log.error("Failed to open recent items file for writing.")
- self.inform.emit('[ERROR_NOTCL] %s' %
- _('Failed to open recent projects file for writing.'))
- return
- json.dump(self.recent_projects, fp, default=to_dict, indent=2, sort_keys=True)
- fp.close()
- # Re-build the recent items menu
- self.setup_recent_items()
- def new_object(self, kind, name, initialize, active=True, fit=True, plot=True, autoselected=True):
- """
- Creates a new specialized FlatCAMObj and attaches it to the application,
- this is, updates the GUI accordingly, any other records and plots it.
- This method is thread-safe.
- Notes:
- * If the name is in use, the self.collection will modify it
- when appending it to the collection. There is no need to handle
- name conflicts here.
- :param kind: The kind of object to create. One of 'gerber', 'excellon', 'cncjob' and 'geometry'.
- :type kind: str
- :param name: Name for the object.
- :type name: str
- :param initialize: Function to run after creation of the object but before it is attached to the application.
- The function is called with 2 parameters: the new object and the App instance.
- :type initialize: function
- :param active:
- :param fit:
- :param plot: If to plot the resulting object
- :param autoselected: if the resulting object is autoselected in the Project tab and therefore in the
- self.colleaction
- :return: None
- :rtype: None
- """
- App.log.debug("new_object()")
- obj_plot = plot
- obj_autoselected = autoselected
- t0 = time.time() # Debug
- # ## Create object
- classdict = {
- "gerber": FlatCAMGerber,
- "excellon": FlatCAMExcellon,
- "cncjob": FlatCAMCNCjob,
- "geometry": FlatCAMGeometry,
- "script": FlatCAMScript,
- "document": FlatCAMDocument
- }
- App.log.debug("Calling object constructor...")
- obj = classdict[kind](name)
- obj.units = self.options["units"] # TODO: The constructor should look at defaults.
- # Set options from "Project options" form
- self.options_read_form()
- # IMPORTANT
- # The key names in defaults and options dictionary's are not random:
- # they have to have in name first the type of the object (geometry, excellon, cncjob and gerber) or how it's
- # called here, the 'kind' followed by an underline. The function called above (self.options_read_form()) copy
- # the options from project options form into the self.options. After that, below, depending on the type of
- # object that is created, it will strip the name of the object and the underline (if the original key was
- # let's say "excellon_toolchange", it will strip the excellon_) and to the obj.options the key will become
- # "toolchange"
- for option in self.options:
- if option.find(kind + "_") == 0:
- oname = option[len(kind) + 1:]
- obj.options[oname] = self.options[option]
- obj.isHovering = False
- obj.notHovering = True
- # Initialize as per user request
- # User must take care to implement initialize
- # in a thread-safe way as is is likely that we
- # have been invoked in a separate thread.
- t1 = time.time()
- self.log.debug("%f seconds before initialize()." % (t1 - t0))
- try:
- return_value = initialize(obj, self)
- except Exception as e:
- msg = '[ERROR_NOTCL] %s' % \
- _("An internal error has ocurred. See shell.\n")
- msg += _("Object ({kind}) failed because: {error} \n\n").format(kind=kind, error=str(e))
- msg += traceback.format_exc()
- self.inform.emit(msg)
- # if str(e) == "Empty Geometry":
- # self.inform.emit("[ERROR_NOTCL] )
- # else:
- # self.inform.emit("[ERROR] Object (%s) failed because: %s" % (kind, str(e)))
- return "fail"
- t2 = time.time()
- self.log.debug("%f seconds executing initialize()." % (t2 - t1))
- if return_value == 'fail':
- log.debug("Object (%s) parsing and/or geometry creation failed." % kind)
- return "fail"
- # Check units and convert if necessary
- # This condition CAN be true because initialize() can change obj.units
- if self.options["units"].upper() != obj.units.upper():
- self.inform.emit('%s: %s' %
- (_("Converting units to "), self.options["units"]))
- obj.convert_units(self.options["units"])
- t3 = time.time()
- self.log.debug("%f seconds converting units." % (t3 - t2))
- # Create the bounding box for the object and then add the results to the obj.options
- # But not for Scripts or for Documents
- if kind != 'document' and kind != 'script':
- try:
- xmin, ymin, xmax, ymax = obj.bounds()
- obj.options['xmin'] = xmin
- obj.options['ymin'] = ymin
- obj.options['xmax'] = xmax
- obj.options['ymax'] = ymax
- except Exception as e:
- log.warning("The object has no bounds properties. %s" % str(e))
- return "fail"
- # update the KeyWords list with the name of the file
- self.myKeywords.append(obj.options['name'])
- FlatCAMApp.App.log.debug("Moving new object back to main thread.")
- # Move the object to the main thread and let the app know that it is available.
- obj.moveToThread(self.main_thread)
- self.object_created.emit(obj, obj_plot, obj_autoselected)
- return obj
- def new_excellon_object(self):
- """
- Creates a new, blank Excellon object.
- :return: None
- """
- self.report_usage("new_excellon_object()")
- self.new_object('excellon', 'new_exc', lambda x, y: None, plot=False)
- def new_geometry_object(self):
- """
- Creates a new, blank and single-tool Geometry object.
- :return: None
- """
- self.report_usage("new_geometry_object()")
- def initialize(obj, self):
- obj.multitool = False
- self.new_object('geometry', 'new_geo', initialize, plot=False)
- def new_gerber_object(self):
- """
- Creates a new, blank Gerber object.
- :return: None
- """
- self.report_usage("new_gerber_object()")
- def initialize(grb_obj, self):
- grb_obj.multitool = False
- grb_obj.source_file = []
- grb_obj.multigeo = False
- grb_obj.follow = False
- grb_obj.apertures = {}
- grb_obj.solid_geometry = []
- try:
- grb_obj.options['xmin'] = 0
- grb_obj.options['ymin'] = 0
- grb_obj.options['xmax'] = 0
- grb_obj.options['ymax'] = 0
- except KeyError:
- pass
- self.new_object('gerber', 'new_grb', initialize, plot=False)
- def new_script_object(self, name=None, text=None):
- """
- Creates a new, blank TCL Script object.
- :param name: a name for the new object
- :return: None
- """
- self.report_usage("new_script_object()")
- if text is not None:
- new_source_file = text
- else:
- new_source_file = _(
- "#\n"
- "# CREATE A NEW FLATCAM TCL SCRIPT\n"
- "# TCL Tutorial here: https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n"
- "#\n\n"
- "# FlatCAM commands list:\n"
- "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, AlignDrillGrid, ClearShell, "
- "ClearCopper,\n"
- "# Cncjob, Cutout, Delete, Drillcncjob, ExportGcode, ExportSVG, Exteriors, GeoCutout, GeoUnion, "
- "GetNames,\n"
- "# GetSys, ImportSvg, Interiors, Isolate, Follow, JoinExcellon, JoinGeometry, ListSys, MillDrills,\n"
- "# MillSlots, Mirror, New, NewGeometry, Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n"
- "# Options, Paint, Panelize, Plot, SaveProject, SaveSys, Scale, SetActive, SetSys, Skew, "
- "SubtractPoly,\n"
- "# SubtractRectangle, Version, WriteGCode\n"
- "#\n\n"
- )
- def initialize(obj, self):
- obj.source_file = deepcopy(new_source_file)
- if name is None:
- outname = 'new_script'
- else:
- outname = name
- self.new_object('script', outname, initialize, plot=False)
- def new_document_object(self):
- """
- Creates a new, blank Document object.
- :return: None
- """
- self.report_usage("new_document_object()")
- def initialize(obj, self):
- obj.source_file = ""
- self.new_object('document', 'new_document', initialize, plot=False)
- def on_object_created(self, obj, plot, autoselect):
- """
- Event callback for object creation.
- It will add the new object to the collection. After that it will plot the object in a threaded way
- :param obj: The newly created FlatCAM object.
- :param plot: if the newly create object t obe plotted
- :param autoselect: if the newly created object to be autoselected after creation
- :return: None
- """
- t0 = time.time() # DEBUG
- self.log.debug("on_object_created()")
- # The Collection might change the name if there is a collision
- self.collection.append(obj)
- # after adding the object to the collection always update the list of objects that are in the collection
- self.all_objects_list = self.collection.get_list()
- # self.inform.emit('[selected] %s created & selected: %s' %
- # (str(obj.kind).capitalize(), str(obj.options['name'])))
- if obj.kind == 'gerber':
- self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
- kind=obj.kind.capitalize(), color='green', name=str(obj.options['name'])))
- elif obj.kind == 'excellon':
- self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
- kind=obj.kind.capitalize(), color='brown', name=str(obj.options['name'])))
- elif obj.kind == 'cncjob':
- self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
- kind=obj.kind.capitalize(), color='blue', name=str(obj.options['name'])))
- elif obj.kind == 'geometry':
- self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
- kind=obj.kind.capitalize(), color='red', name=str(obj.options['name'])))
- elif obj.kind == 'script':
- self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
- kind=obj.kind.capitalize(), color='orange', name=str(obj.options['name'])))
- elif obj.kind == 'document':
- self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
- kind=obj.kind.capitalize(), color='darkCyan', name=str(obj.options['name'])))
- # update the SHELL auto-completer model with the name of the new object
- self.shell._edit.set_model_data(self.myKeywords)
- if autoselect:
- # select the just opened object but deselect the previous ones
- self.collection.set_all_inactive()
- self.collection.set_active(obj.options["name"])
- else:
- self.collection.set_all_inactive()
- # here it is done the object plotting
- def worker_task(t_obj):
- with self.proc_container.new(_("Plotting")):
- if isinstance(t_obj, FlatCAMCNCjob):
- t_obj.plot(kind=self.defaults["cncjob_plot_kind"])
- else:
- t_obj.plot()
- t1 = time.time() # DEBUG
- self.log.debug("%f seconds adding object and plotting." % (t1 - t0))
- self.object_plotted.emit(t_obj)
- # Send to worker
- # self.worker.add_task(worker_task, [self])
- if plot is True:
- self.worker_task.emit({'fcn': worker_task, 'params': [obj]})
- def on_object_changed(self, obj):
- """
- Called whenever the geometry of the object was changed in some way.
- This require the update of it's bounding values so it can be the selected on canvas.
- Update the bounding box data from obj.options
- :param obj: the object that was changed
- :return: None
- """
- xmin, ymin, xmax, ymax = obj.bounds()
- obj.options['xmin'] = xmin
- obj.options['ymin'] = ymin
- obj.options['xmax'] = xmax
- obj.options['ymax'] = ymax
- log.debug("Object changed, updating the bounding box data on self.options")
- # delete the old selection shape
- self.delete_selection_shape()
- self.should_we_save = True
- def on_object_plotted(self, obj):
- """
- Callback called whenever the plotted object needs to be fit into the viewport (canvas)
- :param obj: object to be fit into view
- :return: None
- """
- self.on_zoom_fit(None)
- def options_read_form(self):
- """
- Same as it's equivalent from the defaults.
- self.options use to store the preferences per project. No longer used.
- :return: None
- """
- for option in self.options_form_fields:
- self.options[option] = self.options_form_fields[option].get_value()
- def options_write_form(self):
- """
- Same as it's equivalent from the defaults.
- self.options use to store the preferences per project. No longer used.
- :return: None
- """
- for option in self.options:
- self.options_write_form_field(option)
- def options_write_form_field(self, field):
- """
- Same as it's equivalent from the defaults.
- self.options use to store the preferences per project. No longer used.
- :return: None
- """
- try:
- self.options_form_fields[field].set_value(self.options[field])
- except KeyError:
- # Changed from error to debug. This allows to have data stored
- # which is not user-editable.
- # self.log.debug("options_write_form_field(): No field for: %s" % field)
- pass
- def on_about(self):
- """
- Displays the "about" dialog found in the Menu --> Help.
- :return: None
- """
- self.report_usage("on_about")
- version = self.version
- version_date = self.version_date
- beta = self.beta
- class AboutDialog(QtWidgets.QDialog):
- def __init__(self, parent=None):
- QtWidgets.QDialog.__init__(self, parent)
- # Icon and title
- self.setWindowIcon(parent.app_icon)
- self.setWindowTitle(_("About FlatCAM"))
- self.resize(600, 200)
- # self.setStyleSheet("background-image: url(share/flatcam_icon256.png); background-attachment: fixed")
- # self.setStyleSheet(
- # "border-image: url(share/flatcam_icon256.png) 0 0 0 0 stretch stretch; "
- # "background-attachment: fixed"
- # )
- # bgimage = QtGui.QImage('share/flatcam_icon256.png')
- # s_bgimage = bgimage.scaled(QtCore.QSize(self.frameGeometry().width(), self.frameGeometry().height()))
- # palette = QtGui.QPalette()
- # palette.setBrush(10, QtGui.QBrush(bgimage)) # 10 = Windowrole
- # self.setPalette(palette)
- logo = QtWidgets.QLabel()
- logo.setPixmap(QtGui.QPixmap('share/flatcam_icon256.png'))
- title = QtWidgets.QLabel(
- "<font size=8><B>FlatCAM</B></font><BR>"
- "{title}<BR>"
- "<BR>"
- "<BR>"
- # "<B>{devel}</B> "
- "<a href = \"https://bitbucket.org/jpcgt/flatcam/src/Beta/\"><B>{devel}</B></a><BR>"
- # "<b>{down}</B> area "
- "<a href = \"https://bitbucket.org/jpcgt/flatcam/downloads/\"><b>{down}</B></a><BR>"
- # "<b> {issue}</B> "
- "<a href = \"https://bitbucket.org/jpcgt/flatcam/issues?status=new&status=open/\">"
- "<B>{issue}</B></a><BR>".format(
- title=_("2D Computer-Aided Printed Circuit Board Manufacturing"),
- devel=_("Development"),
- down=_("DOWNLOAD"),
- issue=_("Issue tracker"))
- )
- title.setOpenExternalLinks(True)
- closebtn = QtWidgets.QPushButton(_("Close"))
- tab_widget = QtWidgets.QTabWidget()
- description_label = QtWidgets.QLabel(
- "FlatCAM {version} {beta} ({date}) - {arch}<br>"
- "<a href = \"http://flatcam.org/\">http://flatcam.org</a><br>".format(
- version=version,
- beta=('BETA' if beta else ''),
- date=version_date,
- arch=platform.architecture()[0])
- )
- description_label.setOpenExternalLinks(True)
- license_label = QtWidgets.QLabel(
- _(
- '(c) Copyright 2014 Juan Pablo Caram.\n\n'
- 'Licensed under the MIT license:\n'
- 'http://www.opensource.org/licenses/mit-license.php\n\n'
- 'Permission is hereby granted, free of charge, to any person obtaining a copy\n'
- 'of this software and associated documentation files (the "Software"), to deal\n'
- 'in the Software without restriction, including without limitation the rights\n'
- 'to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n'
- 'copies of the Software, and to permit persons to whom the Software is\n'
- ' furnished to do so, subject to the following conditions:\n\n'
- 'The above copyright notice and this permission notice shall be included in\n'
- 'all copies or substantial portions of the Software.\n\n'
- 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n'
- 'IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n'
- 'FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n'
- 'AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n'
- 'LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n'
- 'OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n'
- 'THE SOFTWARE.'
- )
- )
- license_label.setOpenExternalLinks(True)
- attributions_label = QtWidgets.QLabel(
- _(
- 'Some of the icons used are from the following sources: <BR>'
- '<div>Icons made by <a href="https://www.flaticon.com/authors/freepik" '
- 'title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" '
- 'title="Flaticon">www.flaticon.com</a></div><br>'
- 'Icons by <a target="_blank" href="https://icons8.com">Icons8</a>'
- )
- )
- # layouts
- layout1 = QtWidgets.QVBoxLayout()
- layout1_1 = QtWidgets.QHBoxLayout()
- layout1_2 = QtWidgets.QHBoxLayout()
- layout2 = QtWidgets.QHBoxLayout()
- layout3 = QtWidgets.QHBoxLayout()
- self.setLayout(layout1)
- layout1.addLayout(layout1_1)
- layout1.addLayout(layout1_2)
- layout1.addLayout(layout2)
- layout1.addLayout(layout3)
- layout1_1.addStretch()
- layout1_1.addWidget(description_label)
- layout1_2.addWidget(tab_widget)
- self.splash_tab = QtWidgets.QWidget()
- self.splash_tab.setObjectName("splash_about")
- self.splash_tab_layout = QtWidgets.QHBoxLayout(self.splash_tab)
- self.splash_tab_layout.setContentsMargins(2, 2, 2, 2)
- tab_widget.addTab(self.splash_tab, _("Splash"))
- self.programmmers_tab = QtWidgets.QWidget()
- self.programmmers_tab.setObjectName("programmers_about")
- self.programmmers_tab_layout = QtWidgets.QVBoxLayout(self.programmmers_tab)
- self.programmmers_tab_layout.setContentsMargins(2, 2, 2, 2)
- tab_widget.addTab(self.programmmers_tab, _("Programmers"))
- self.translators_tab = QtWidgets.QWidget()
- self.translators_tab.setObjectName("translators_about")
- self.translators_tab_layout = QtWidgets.QVBoxLayout(self.translators_tab)
- self.translators_tab_layout.setContentsMargins(2, 2, 2, 2)
- tab_widget.addTab(self.translators_tab, _("Translators"))
- self.license_tab = QtWidgets.QWidget()
- self.license_tab.setObjectName("license_about")
- self.license_tab_layout = QtWidgets.QVBoxLayout(self.license_tab)
- self.license_tab_layout.setContentsMargins(2, 2, 2, 2)
- tab_widget.addTab(self.license_tab, _("License"))
- self.attributions_tab = QtWidgets.QWidget()
- self.attributions_tab.setObjectName("attributions_about")
- self.attributions_tab_layout = QtWidgets.QVBoxLayout(self.attributions_tab)
- self.attributions_tab_layout.setContentsMargins(2, 2, 2, 2)
- tab_widget.addTab(self.attributions_tab, _("Attributions"))
- self.splash_tab_layout.addWidget(logo, stretch=0)
- self.splash_tab_layout.addWidget(title, stretch=1)
- pal = QtGui.QPalette()
- pal.setColor(QtGui.QPalette.Background, Qt.white)
- self.prog_form_lay = QtWidgets.QFormLayout()
- self.prog_form_lay.setHorizontalSpacing(20)
- prog_widget = QtWidgets.QWidget()
- prog_widget.setLayout(self.prog_form_lay)
- prog_scroll = QtWidgets.QScrollArea()
- prog_scroll.setWidget(prog_widget)
- prog_scroll.setWidgetResizable(True)
- prog_scroll.setFrameShape(QtWidgets.QFrame.NoFrame)
- prog_scroll.setPalette(pal)
- self.programmmers_tab_layout.addWidget(prog_scroll)
- self.prog_form_lay.addRow(QtWidgets.QLabel('<b>%s</b>' % _("Programmer")),
- QtWidgets.QLabel('<b>%s</b>' % _("Status")))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Juan Pablo Caram"),
- QtWidgets.QLabel('%s' % _("Program Author")))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Denis Hayrullin"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Kamil Sopko"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Marius Stanciu"),
- QtWidgets.QLabel('%s' % _("Maintainer >=2019")))
- self.prog_form_lay.addRow(QtWidgets.QLabel(''))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Alexandru Lazar"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Matthieu Berthomé"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Mike Evans"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Victor Benso"))
- self.prog_form_lay.addRow(QtWidgets.QLabel(''))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Barnaby Walters"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Jørn Sandvik Nilsson"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Lei Zheng"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Marco A Quezada"))
- self.prog_form_lay.addRow(QtWidgets.QLabel(''))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Cedric Dussud"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Chris Hemingway"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Damian Wrobel"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Daniel Sallin"))
- self.prog_form_lay.addRow(QtWidgets.QLabel(''))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Bruno Vunderl"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Gonzalo Lopez"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Jakob Staudt"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Mike Smith"))
- self.prog_form_lay.addRow(QtWidgets.QLabel(''))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Lubos Medovarsky"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Steve Martina"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "Thomas Duffin"))
- self.prog_form_lay.addRow(QtWidgets.QLabel(''))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@Idechix"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@SM"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@grbf"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@Symonty"))
- self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@mgix"))
- self.translator_grid_lay = QtWidgets.QGridLayout()
- # trans_widget = QtWidgets.QWidget()
- # trans_widget.setLayout(self.translator_grid_lay)
- # self.translators_tab_layout.addWidget(trans_widget)
- # self.translators_tab_layout.addStretch()
- trans_widget = QtWidgets.QWidget()
- trans_widget.setLayout(self.translator_grid_lay)
- trans_scroll = QtWidgets.QScrollArea()
- trans_scroll.setWidget(trans_widget)
- trans_scroll.setWidgetResizable(True)
- trans_scroll.setFrameShape(QtWidgets.QFrame.NoFrame)
- trans_scroll.setPalette(pal)
- self.translators_tab_layout.addWidget(trans_scroll)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Language")), 0, 0)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Translator")), 0, 1)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("E-mail")), 0, 2)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Brasilian - Portuguese"), 1, 0)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Carlos Stein"), 1, 1)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 1, 2)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "French"), 2, 0)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Translation)"), 2, 1)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 2, 2)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "German"), 3, 0)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Translation)"), 3, 1)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 2)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Romanian"), 4, 0)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 4, 1)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 4, 2)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Russian"), 5, 0)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Andrey Kultyapov"), 5, 1)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<camellan@yandex.ru>"), 5, 2)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Spanish"), 6, 0)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Translation)"), 6, 1)
- self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 6, 2)
- self.translator_grid_lay.setColumnStretch(0, 0)
- self.translators_tab_layout.addStretch()
- self.license_tab_layout.addWidget(license_label)
- self.license_tab_layout.addStretch()
- self.attributions_tab_layout.addWidget(attributions_label)
- self.attributions_tab_layout.addStretch()
- layout3.addStretch()
- layout3.addWidget(closebtn)
- closebtn.clicked.connect(self.accept)
- AboutDialog(self.ui).exec_()
- def on_file_savedefaults(self):
- """
- Callback for menu item File->Save Defaults. Saves application default options
- ``self.defaults`` to current_defaults.FlatConfig.
- :return: None
- """
- self.save_defaults()
- # def on_app_exit(self):
- # self.report_usage("on_app_exit()")
- #
- # if self.collection.get_list():
- # msgbox = QtWidgets.QMessageBox()
- # # msgbox.setText("<B>Save changes ...</B>")
- # msgbox.setText("There are files/objects opened in FlatCAM. "
- # "\n"
- # "Do you want to Save the project?")
- # msgbox.setWindowTitle("Save changes")
- # msgbox.setWindowIcon(QtGui.QIcon('share/save_as.png'))
- # msgbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No |
- # QtWidgets.QMessageBox.Cancel)
- # msgbox.setDefaultButton(QtWidgets.QMessageBox.Yes)
- #
- # response = msgbox.exec_()
- #
- # if response == QtWidgets.QMessageBox.Yes:
- # self.on_file_saveprojectas(thread=False)
- # elif response == QtWidgets.QMessageBox.Cancel:
- # return
- # self.save_defaults()
- # else:
- # self.save_defaults()
- # log.debug("Application defaults saved ... Exit event.")
- # QtWidgets.qApp.quit()
- def save_defaults(self, silent=False, data_path=None, first_time=False):
- """
- Saves application default options
- ``self.defaults`` to current_defaults.FlatConfig file.
- Save the toolbars visibility status to the preferences file (current_defaults.FlatConfig) to be
- used at the next launch of the application.
- :param silent: whether to display a message in status bar or not; boolean
- :param data_path: the path where to save the preferences file (current_defaults.FlatConfig)
- When the application is portable it should be a mobile location.
- :return: None
- """
- self.report_usage("save_defaults")
- if data_path is None:
- data_path = self.data_path
- # Read options from file
- try:
- f = open(data_path + "/current_defaults.FlatConfig")
- defaults_file_content = f.read()
- f.close()
- except:
- e = sys.exc_info()[0]
- App.log.error("Could not load defaults file.")
- App.log.error(str(e))
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Could not load defaults file."))
- return
- try:
- defaults = json.loads(defaults_file_content)
- except:
- e = sys.exc_info()[0]
- App.log.error("Failed to parse defaults file.")
- App.log.error(str(e))
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to parse defaults file."))
- return
- # Update options
- self.defaults_read_form()
- defaults.update(self.defaults)
- self.propagate_defaults(silent=True)
- # Save the toolbar view
- tb_status = 0
- if self.ui.toolbarfile.isVisible():
- tb_status += 1
- if self.ui.toolbargeo.isVisible():
- tb_status += 2
- if self.ui.toolbarview.isVisible():
- tb_status += 4
- if self.ui.toolbartools.isVisible():
- tb_status += 8
- if self.ui.exc_edit_toolbar.isVisible():
- tb_status += 16
- if self.ui.geo_edit_toolbar.isVisible():
- tb_status += 32
- if self.ui.grb_edit_toolbar.isVisible():
- tb_status += 64
- if self.ui.snap_toolbar.isVisible():
- tb_status += 128
- if self.ui.toolbarshell.isVisible():
- tb_status += 256
- if first_time is False:
- self.defaults["global_toolbar_view"] = tb_status
- # Save update options
- try:
- f = open(data_path + "/current_defaults.FlatConfig", "w")
- json.dump(defaults, f, default=to_dict, indent=2, sort_keys=True)
- f.close()
- except:
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to write defaults to file."))
- return
- if not silent:
- self.inform.emit('[success] %s' %
- _("Preferences saved."))
- def save_factory_defaults(self, silent=False, data_path=None):
- """
- Saves application factory default options
- ``self.defaults`` to factory_defaults.FlatConfig.
- It's a one time job done just after the first install.
- :param silent: whether to display a message in status bar or not; boolean
- :param data_path: the path where to save the default preferences file (factory_defaults.FlatConfig)
- When the application is portable it should be a mobile location.
- :return: None
- """
- self.report_usage("save_factory_defaults")
- if data_path is None:
- data_path = self.data_path
- # Read options from file
- try:
- f_f_def = open(data_path + "/factory_defaults.FlatConfig")
- factory_defaults_file_content = f_f_def.read()
- f_f_def.close()
- except:
- e = sys.exc_info()[0]
- App.log.error("Could not load factory defaults file.")
- App.log.error(str(e))
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Could not load factory defaults file."))
- return
- try:
- factory_defaults = json.loads(factory_defaults_file_content)
- except:
- e = sys.exc_info()[0]
- App.log.error("Failed to parse factory defaults file.")
- App.log.error(str(e))
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to parse factory defaults file."))
- return
- # Update options
- self.defaults_read_form()
- factory_defaults.update(self.defaults)
- self.propagate_defaults(silent=True)
- # Save update options
- try:
- f_f_def_s = open(data_path + "/factory_defaults.FlatConfig", "w")
- json.dump(factory_defaults, f_f_def_s, default=to_dict, indent=2, sort_keys=True)
- f_f_def_s.close()
- except:
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to write factory defaults to file."))
- return
- if silent is False:
- self.inform.emit(_("Factory defaults saved."))
- def final_save(self):
- """
- Callback for doing a preferences save to file whenever the application is about to quit.
- If the project has changes, it will ask the user to save the project.
- :return: None
- """
- if self.save_in_progress:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Application is saving the project. Please wait ..."))
- return
- if self.should_we_save and self.collection.get_list():
- msgbox = QtWidgets.QMessageBox()
- msgbox.setText(_("There are files/objects modified in FlatCAM. "
- "\n"
- "Do you want to Save the project?"))
- msgbox.setWindowTitle(_("Save changes"))
- msgbox.setWindowIcon(QtGui.QIcon('share/save_as.png'))
- bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
- bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
- bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.RejectRole)
- msgbox.setDefaultButton(bt_yes)
- msgbox.exec_()
- response = msgbox.clickedButton()
- if response == bt_yes:
- try:
- self.trayIcon.hide()
- except:
- pass
- self.on_file_saveprojectas(use_thread=True, quit_action=True)
- elif response == bt_no:
- try:
- self.trayIcon.hide()
- except:
- pass
- self.quit_application()
- elif response == bt_cancel:
- return
- else:
- try:
- self.trayIcon.hide()
- except:
- pass
- self.quit_application()
- def quit_application(self):
- """
- Called (as a pyslot or not) when the application is quit.
- :return: None
- """
- self.save_defaults()
- log.debug("App.final_save() --> App Defaults saved.")
- if self.cmd_line_headless != 1:
- # save app state to file
- settings = QSettings("Open Source", "FlatCAM")
- settings.setValue('saved_gui_state', self.ui.saveState())
- settings.setValue('maximized_gui', self.ui.isMaximized())
- settings.setValue('language', self.ui.general_defaults_form.general_app_group.language_cb.get_value())
- settings.setValue(
- 'notebook_font_size',
- self.ui.general_defaults_form.general_gui_set_group.notebook_font_size_spinner.get_value()
- )
- settings.setValue('axis_font_size',
- self.ui.general_defaults_form.general_gui_set_group.axis_font_size_spinner.get_value())
- settings.setValue(
- 'textbox_font_size',
- self.ui.general_defaults_form.general_gui_set_group.textbox_font_size_spinner.get_value()
- )
- settings.setValue('toolbar_lock', self.ui.lock_action.isChecked())
- # This will write the setting to the platform specific storage.
- del settings
- log.debug("App.final_save() --> App UI state saved.")
- QtWidgets.qApp.quit()
- def on_portable_checked(self, state):
- """
- Callback called when the checkbox in Preferences GUI is checked.
- It will set the application as portable by creating the preferences and recent files in the
- 'config' folder found in the FlatCAM installation folder.
- :param state: boolean, the state of the checkbox when clicked/checked
- :return:
- """
- line_no = 0
- data = None
- if sys.platform != 'win32':
- # this won't work in Linux or MacOS
- return
- # test if the app was frozen and choose the path for the configuration file
- if getattr(sys, "frozen", False) is True:
- current_data_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '\\config'
- else:
- current_data_path = os.path.dirname(os.path.realpath(__file__)) + '\\config'
- config_file = current_data_path + '\\configuration.txt'
- try:
- with open(config_file, 'r') as f:
- try:
- data = f.readlines()
- except Exception as e:
- log.debug('App.__init__() -->%s' % str(e))
- return
- except FileNotFoundError:
- pass
- for line in data:
- line = line.strip('\n')
- param = str(line).rpartition('=')
- if param[0] == 'portable':
- break
- line_no += 1
- if state:
- data[line_no] = 'portable=True\n'
- # create the new defauults files
- # create current_defaults.FlatConfig file if there is none
- try:
- f = open(current_data_path + '/current_defaults.FlatConfig')
- f.close()
- except IOError:
- App.log.debug('Creating empty current_defaults.FlatConfig')
- f = open(current_data_path + '/current_defaults.FlatConfig', 'w')
- json.dump({}, f)
- f.close()
- # create factory_defaults.FlatConfig file if there is none
- try:
- f = open(current_data_path + '/factory_defaults.FlatConfig')
- f.close()
- except IOError:
- App.log.debug('Creating empty factory_defaults.FlatConfig')
- f = open(current_data_path + '/factory_defaults.FlatConfig', 'w')
- json.dump({}, f)
- f.close()
- try:
- f = open(current_data_path + '/recent.json')
- f.close()
- except IOError:
- App.log.debug('Creating empty recent.json')
- f = open(current_data_path + '/recent.json', 'w')
- json.dump([], f)
- f.close()
- try:
- fp = open(current_data_path + '/recent_projects.json')
- fp.close()
- except IOError:
- App.log.debug('Creating empty recent_projects.json')
- fp = open(current_data_path + '/recent_projects.json', 'w')
- json.dump([], fp)
- fp.close()
- # save the current defaults to the new defaults file
- self.save_defaults(silent=True, data_path=current_data_path)
- self.save_factory_defaults(silent=True, data_path=current_data_path)
- else:
- data[line_no] = 'portable=False\n'
- with open(config_file, 'w') as f:
- f.writelines(data)
- def on_toggle_shell(self):
- """
- Toggle shell: if is visible close it, if it is closed then open it
- :return:
- """
- self.report_usage("on_toggle_shell()")
- if self.ui.shell_dock.isVisible():
- self.ui.shell_dock.hide()
- else:
- self.ui.shell_dock.show()
- def on_register_files(self, obj_type=None):
- """
- Called whenever there is a need to register file extensions with FlatCAM.
- Works only in Windows and should be called only when FlatCAM is run in Windows.
- :param obj_type: the type of object to be register for.
- Can be: 'gerber', 'excellon' or 'gcode'. 'geometry' is not used for the moment.
- :return: None
- """
- log.debug("Manufacturing files extensions are registered with FlatCAM.")
- new_reg_path = 'Software\\Classes\\'
- # find if the current user is admin
- try:
- is_admin = os.getuid() == 0
- except AttributeError:
- is_admin = ctypes.windll.shell32.IsUserAnAdmin() == 1
- if is_admin is True:
- root_path = winreg.HKEY_LOCAL_MACHINE
- else:
- root_path = winreg.HKEY_CURRENT_USER
- # create the keys
- def set_reg(name, root_path, new_reg_path, value):
- try:
- winreg.CreateKey(root_path, new_reg_path)
- with winreg.OpenKey(root_path, new_reg_path, 0, winreg.KEY_WRITE) as registry_key:
- winreg.SetValueEx(registry_key, name, 0, winreg.REG_SZ, value)
- return True
- except WindowsError:
- return False
- # delete key in registry
- def delete_reg(root_path, reg_path, key_to_del):
- key_to_del_path = reg_path + key_to_del
- try:
- winreg.DeleteKey(root_path, key_to_del_path)
- return True
- except WindowsError:
- return False
- if obj_type is None or obj_type == 'excellon':
- exc_list = self.ui.util_defaults_form.fa_excellon_group.exc_list_text.get_value().replace(' ', '').split(',')
- exc_list = [x for x in exc_list if x != '']
- # register all keys in the Preferences window
- for ext in exc_list:
- new_k = new_reg_path + '.%s' % ext
- set_reg('', root_path=root_path, new_reg_path=new_k, value='FlatCAM')
- # and unregister those that are no longer in the Preferences windows but are in the file
- for ext in self.defaults["fa_excellon"].replace(' ', '').split(','):
- if ext not in exc_list:
- delete_reg(root_path=root_path, reg_path=new_reg_path, key_to_del='.%s' % ext)
- # now write the updated extensions to the self.defaults
- # new_ext = ''
- # for ext in exc_list:
- # new_ext = new_ext + ext + ', '
- # self.defaults["fa_excellon"] = new_ext
- self.inform.emit('[success] %s' % _("Selected Excellon file extensions registered with FlatCAM."))
- if obj_type is None or obj_type == 'gcode':
- gco_list = self.ui.util_defaults_form.fa_gcode_group.gco_list_text.get_value().replace(' ', '').split(',')
- gco_list = [x for x in gco_list if x != '']
- # register all keys in the Preferences window
- for ext in gco_list:
- new_k = new_reg_path + '.%s' % ext
- set_reg('', root_path=root_path, new_reg_path=new_k, value='FlatCAM')
- # and unregister those that are no longer in the Preferences windows but are in the file
- for ext in self.defaults["fa_gcode"].replace(' ', '').split(','):
- if ext not in gco_list:
- delete_reg(root_path=root_path, reg_path=new_reg_path, key_to_del='.%s' % ext)
- # now write the updated extensions to the self.defaults
- # new_ext = ''
- # for ext in gco_list:
- # new_ext = new_ext + ext + ', '
- # self.defaults["fa_gcode"] = new_ext
- self.inform.emit('[success] %s' %
- _("Selected GCode file extensions registered with FlatCAM."))
- if obj_type is None or obj_type == 'gerber':
- grb_list = self.ui.util_defaults_form.fa_gerber_group.grb_list_text.get_value().replace(' ', '').split(',')
- grb_list = [x for x in grb_list if x != '']
- # register all keys in the Preferences window
- for ext in grb_list:
- new_k = new_reg_path + '.%s' % ext
- set_reg('', root_path=root_path, new_reg_path=new_k, value='FlatCAM')
- # and unregister those that are no longer in the Preferences windows but are in the file
- for ext in self.defaults["fa_gerber"].replace(' ', '').split(','):
- if ext not in grb_list:
- delete_reg(root_path=root_path, reg_path=new_reg_path, key_to_del='.%s' % ext)
- # now write the updated extensions to the self.defaults
- # new_ext = ''
- # for ext in grb_list:
- # new_ext = new_ext + ext + ', '
- # self.defaults["fa_gerber"] = new_ext
- self.inform.emit('[success] %s' %
- _("Selected Gerber file extensions registered with FlatCAM."))
- def add_extension(self, ext_type):
- if ext_type == 'excellon':
- new_ext = self.ui.util_defaults_form.fa_excellon_group.ext_entry.get_value()
- if new_ext == '':
- return
- old_val = self.ui.util_defaults_form.fa_excellon_group.exc_list_text.get_value().replace(' ', '').split(',')
- if new_ext in old_val:
- return
- old_val.append(new_ext)
- old_val.sort()
- self.ui.util_defaults_form.fa_excellon_group.exc_list_text.set_value(', '.join(old_val))
- if ext_type == 'gcode':
- new_ext = self.ui.util_defaults_form.fa_gcode_group.ext_entry.get_value()
- if new_ext == '':
- return
- old_val = self.ui.util_defaults_form.fa_gcode_group.gco_list_text.get_value().replace(' ', '').split(',')
- if new_ext in old_val:
- return
- old_val.append(new_ext)
- old_val.sort()
- self.ui.util_defaults_form.fa_gcode_group.gco_list_text.set_value(', '.join(old_val))
- if ext_type == 'gerber':
- new_ext = self.ui.util_defaults_form.fa_gerber_group.ext_entry.get_value()
- if new_ext == '':
- return
- old_val = self.ui.util_defaults_form.fa_gerber_group.grb_list_text.get_value().replace(' ', '').split(',')
- if new_ext in old_val:
- return
- old_val.append(new_ext)
- old_val.sort()
- self.ui.util_defaults_form.fa_gerber_group.grb_list_text.set_value(', '.join(old_val))
- if ext_type == 'keyword':
- new_kw = self.ui.util_defaults_form.kw_group.kw_entry.get_value()
- if new_kw == '':
- return
- old_val = self.ui.util_defaults_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',')
- if new_kw in old_val:
- return
- old_val.append(new_kw)
- old_val.sort()
- self.ui.util_defaults_form.kw_group.kw_list_text.set_value(', '.join(old_val))
- # update the self.myKeywords so the model is updated
- self.autocomplete_kw_list = \
- self.ui.util_defaults_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',')
- self.myKeywords = self.tcl_commands_list + self.autocomplete_kw_list + self.tcl_keywords
- self.shell._edit.set_model_data(self.myKeywords)
- def del_extension(self, ext_type):
- if ext_type == 'excellon':
- new_ext = self.ui.util_defaults_form.fa_excellon_group.ext_entry.get_value()
- if new_ext == '':
- return
- old_val = self.ui.util_defaults_form.fa_excellon_group.exc_list_text.get_value().replace(' ', '').split(',')
- if new_ext not in old_val:
- return
- old_val.remove(new_ext)
- old_val.sort()
- self.ui.util_defaults_form.fa_excellon_group.exc_list_text.set_value(', '.join(old_val))
- if ext_type == 'gcode':
- new_ext = self.ui.util_defaults_form.fa_gcode_group.ext_entry.get_value()
- if new_ext == '':
- return
- old_val = self.ui.util_defaults_form.fa_gcode_group.gco_list_text.get_value().replace(' ', '').split(',')
- if new_ext not in old_val:
- return
- old_val.remove(new_ext)
- old_val.sort()
- self.ui.util_defaults_form.fa_gcode_group.gco_list_text.set_value(', '.join(old_val))
- if ext_type == 'gerber':
- new_ext = self.ui.util_defaults_form.fa_gerber_group.ext_entry.get_value()
- if new_ext == '':
- return
- old_val = self.ui.util_defaults_form.fa_gerber_group.grb_list_text.get_value().replace(' ', '').split(',')
- if new_ext not in old_val:
- return
- old_val.remove(new_ext)
- old_val.sort()
- self.ui.util_defaults_form.fa_gerber_group.grb_list_text.set_value(', '.join(old_val))
- if ext_type == 'keyword':
- new_kw = self.ui.util_defaults_form.kw_group.kw_entry.get_value()
- if new_kw == '':
- return
- old_val = self.ui.util_defaults_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',')
- if new_kw not in old_val:
- return
- old_val.remove(new_kw)
- old_val.sort()
- self.ui.util_defaults_form.kw_group.kw_list_text.set_value(', '.join(old_val))
- # update the self.myKeywords so the model is updated
- self.autocomplete_kw_list = \
- self.ui.util_defaults_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',')
- self.myKeywords = self.tcl_commands_list + self.autocomplete_kw_list + self.tcl_keywords
- self.shell._edit.set_model_data(self.myKeywords)
- def restore_extensions(self, ext_type):
- if ext_type == 'excellon':
- # don't add 'txt' to the associations (too many files are .txt and not Excellon) but keep it in the list
- # for the ability to open Excellon files with .txt extension
- new_exc_list = deepcopy(self.exc_list)
- try:
- new_exc_list.remove('txt')
- except ValueError:
- pass
- self.ui.util_defaults_form.fa_excellon_group.exc_list_text.set_value(', '.join(new_exc_list))
- if ext_type == 'gcode':
- self.ui.util_defaults_form.fa_gcode_group.gco_list_text.set_value(', '.join(self.gcode_list))
- if ext_type == 'gerber':
- self.ui.util_defaults_form.fa_gerber_group.grb_list_text.set_value(', '.join(self.grb_list))
- if ext_type == 'keyword':
- self.ui.util_defaults_form.kw_group.kw_list_text.set_value(', '.join(self.default_keywords))
- # update the self.myKeywords so the model is updated
- self.autocomplete_kw_list = self.default_keywords
- self.myKeywords = self.tcl_commands_list + self.autocomplete_kw_list + self.tcl_keywords
- self.shell._edit.set_model_data(self.myKeywords)
- def delete_all_extensions(self, ext_type):
- if ext_type == 'excellon':
- self.ui.util_defaults_form.fa_excellon_group.exc_list_text.set_value('')
- if ext_type == 'gcode':
- self.ui.util_defaults_form.fa_gcode_group.gco_list_text.set_value('')
- if ext_type == 'gerber':
- self.ui.util_defaults_form.fa_gerber_group.grb_list_text.set_value('')
- if ext_type == 'keyword':
- self.ui.util_defaults_form.kw_group.kw_list_text.set_value('')
- # update the self.myKeywords so the model is updated
- self.myKeywords = self.tcl_commands_list + self.tcl_keywords
- self.shell._edit.set_model_data(self.myKeywords)
- def on_edit_join(self, name=None):
- """
- Callback for Edit->Join. Joins the selected geometry objects into
- a new one.
- :return: None
- """
- self.report_usage("on_edit_join()")
- obj_name_single = str(name) if name else "Combo_SingleGeo"
- obj_name_multi = str(name) if name else "Combo_MultiGeo"
- geo_type_list = set()
- objs = self.collection.get_selected()
- if len(objs) < 2:
- self.inform.emit('[ERROR_NOTCL] %s: %d' %
- (_("At least two objects are required for join. Objects currently selected"), len(objs)))
- return 'fail'
- for obj in objs:
- geo_type_list.add(obj.multigeo)
- # if len(geo_type_list) == 1 means that all list elements are the same
- if len(geo_type_list) != 1:
- self.inform.emit('[ERROR] %s' %
- _("Failed join. The Geometry objects are of different types.\n"
- "At least one is MultiGeo type and the other is SingleGeo type. A possibility is to "
- "convert from one to another and retry joining \n"
- "but in the case of converting from MultiGeo to SingleGeo, informations may be lost and "
- "the result may not be what was expected. \n"
- "Check the generated GCODE."))
- return
- # if at least one True object is in the list then due of the previous check, all list elements are True objects
- if True in geo_type_list:
- def initialize(obj, app):
- FlatCAMGeometry.merge(self, geo_list=objs, geo_final=obj, multigeo=True)
- # rename all the ['name] key in obj.tools[tooluid]['data'] to the obj_name_multi
- for v in obj.tools.values():
- v['data']['name'] = obj_name_multi
- self.new_object("geometry", obj_name_multi, initialize)
- else:
- def initialize(obj, app):
- FlatCAMGeometry.merge(self, geo_list=objs, geo_final=obj, multigeo=False)
- # rename all the ['name] key in obj.tools[tooluid]['data'] to the obj_name_multi
- for v in obj.tools.values():
- v['data']['name'] = obj_name_single
- self.new_object("geometry", obj_name_single, initialize)
- self.should_we_save = True
- def on_edit_join_exc(self):
- """
- Callback for Edit->Join Excellon. Joins the selected Excellon objects into
- a new Excellon.
- :return: None
- """
- self.report_usage("on_edit_join_exc()")
- objs = self.collection.get_selected()
- for obj in objs:
- if not isinstance(obj, FlatCAMExcellon):
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Excellon joining works only on Excellon objects."))
- return
- if len(objs) < 2:
- self.inform.emit('[ERROR_NOTCL] %s: %d' %
- (_("At least two objects are required for join. Objects currently selected"), len(objs)))
- return 'fail'
- def initialize(obj, app):
- FlatCAMExcellon.merge(self, exc_list=objs, exc_final=obj)
- self.new_object("excellon", 'Combo_Excellon', initialize)
- self.should_we_save = True
- def on_edit_join_grb(self):
- """
- Callback for Edit->Join Gerber. Joins the selected Gerber objects into
- a new Gerber object.
- :return: None
- """
- self.report_usage("on_edit_join_grb()")
- objs = self.collection.get_selected()
- for obj in objs:
- if not isinstance(obj, FlatCAMGerber):
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Gerber joining works only on Gerber objects."))
- return
- if len(objs) < 2:
- self.inform.emit('[ERROR_NOTCL] %s: %d' %
- (_("At least two objects are required for join. Objects currently selected"), len(objs)))
- return 'fail'
- def initialize(obj, app):
- FlatCAMGerber.merge(self, grb_list=objs, grb_final=obj)
- self.new_object("gerber", 'Combo_Gerber', initialize)
- self.should_we_save = True
- def on_convert_singlegeo_to_multigeo(self):
- """
- Called for converting a Geometry object from single-geo to multi-geo.
- Single-geo Geometry objects store their geometry data into self.solid_geometry.
- Multi-geo Geometry objects store their geometry data into the self.tools dictionary, each key (a tool actually)
- having as a value another dictionary. This value dictionary has one of it's keys 'solid_geometry' which holds
- the solid-geometry of that tool.
- :return: None
- """
- self.report_usage("on_convert_singlegeo_to_multigeo()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Select a Geometry Object and try again."))
- return
- if not isinstance(obj, FlatCAMGeometry):
- self.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Expected a FlatCAMGeometry, got"), type(obj)))
- return
- obj.multigeo = True
- for tooluid, dict_value in obj.tools.items():
- dict_value['solid_geometry'] = deepcopy(obj.solid_geometry)
- if not isinstance(obj.solid_geometry, list):
- obj.solid_geometry = [obj.solid_geometry]
- obj.solid_geometry[:] = []
- obj.plot()
- self.should_we_save = True
- self.inform.emit('[success] %s' %
- _("A Geometry object was converted to MultiGeo type."))
- def on_convert_multigeo_to_singlegeo(self):
- """
- Called for converting a Geometry object from multi-geo to single-geo.
- Single-geo Geometry objects store their geometry data into self.solid_geometry.
- Multi-geo Geometry objects store their geometry data into the self.tools dictionary, each key (a tool actually)
- having as a value another dictionary. This value dictionary has one of it's keys 'solid_geometry' which holds
- the solid-geometry of that tool.
- :return: None
- """
- self.report_usage("on_convert_multigeo_to_singlegeo()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Select a Geometry Object and try again."))
- return
- if not isinstance(obj, FlatCAMGeometry):
- self.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Expected a FlatCAMGeometry, got"), type(obj)))
- return
- obj.multigeo = False
- total_solid_geometry = []
- for tooluid, dict_value in obj.tools.items():
- total_solid_geometry += deepcopy(dict_value['solid_geometry'])
- # clear the original geometry
- dict_value['solid_geometry'][:] = []
- obj.solid_geometry = deepcopy(total_solid_geometry)
- obj.plot()
- self.should_we_save = True
- self.inform.emit('[success] %s' %
- _("A Geometry object was converted to SingleGeo type."))
- def on_options_dict_change(self, field):
- """
- Called whenever a key changed in the self.options dictionary. This dict was used to store the preferences of the
- current project. This feature is no longer used.
- :param field:
- :return:
- """
- self.options_write_form_field(field)
- if field == "units":
- self.set_screen_units(self.options['units'])
- def on_defaults_dict_change(self, field):
- """
- Called whenever a key changed in the self.defaults dictionary. It will set the required GUI element in the
- Edit -> Preferences tab window.
- :param field: the key of the self.defaults dictionary that was changed.
- :return: None
- """
- self.defaults_write_form_field(field)
- if field == "units":
- self.set_screen_units(self.defaults['units'])
- def set_screen_units(self, units):
- """
- Set the FlatCAM units on the status bar.
- :param units: the new measuring units to be displayed in FlatCAM's status bar.
- :return: None
- """
- self.ui.units_label.setText("[" + units.lower() + "]")
- def on_toggle_units(self, no_pref=False):
- """
- Callback for the Units radio-button change in the Preferences tab.
- Changes the application's default units adn for the project too.
- If changing the project's units, the change propagates to all of
- the objects in the project.
- :return: None
- """
- self.report_usage("on_toggle_units")
- if self.toggle_units_ignore:
- return
- new_units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
- # If option is the same, then ignore
- if new_units == self.defaults["units"].upper():
- self.log.debug("on_toggle_units(): Same as defaults, so ignoring.")
- return
- # Options to scale
- dimensions = ['gerber_isotooldia', 'gerber_noncoppermargin', 'gerber_bboxmargin', "gerber_isooverlap",
- "gerber_editor_newsize", "gerber_editor_lin_pitch", "gerber_editor_buff_f"
- 'excellon_drillz', 'excellon_travelz', "excellon_toolchangexy",
- 'excellon_feedrate', 'excellon_feedrate_rapid', 'excellon_toolchangez',
- 'excellon_tooldia', 'excellon_slot_tooldia', 'excellon_endz', "excellon_feedrate_probe",
- "excellon_z_pdepth", "excellon_editor_newdia", "excellon_editor_lin_pitch",
- "excellon_editor_slot_lin_pitch",
- 'geometry_cutz', "geometry_depthperpass", 'geometry_travelz', 'geometry_feedrate',
- 'geometry_feedrate_rapid', "geometry_toolchangez", "geometry_feedrate_z",
- "geometry_toolchangexy", 'geometry_cnctooldia', 'geometry_endz', "geometry_z_pdepth",
- "geometry_feedrate_probe", "geometry_startz",
- 'cncjob_tooldia',
- 'tools_paintmargin', 'tools_painttooldia', 'tools_paintoverlap',
- "tools_ncctools", "tools_nccoverlap", "tools_nccmargin", "tools_ncccutz", "tools_ncctipdia"
- "tools_2sided_drilldia", "tools_film_boundary",
- "tools_cutouttooldia", 'tools_cutoutmargin', 'tools_cutoutgapsize',
- "tools_panelize_constrainx", "tools_panelize_constrainy",
- "tools_calc_vshape_tip_dia", "tools_calc_vshape_cut_z",
- "tools_transform_skew_x", "tools_transform_skew_y", "tools_transform_offset_x",
- "tools_transform_offset_y",
- "tools_solderpaste_tools", "tools_solderpaste_new", "tools_solderpaste_z_start",
- "tools_solderpaste_z_dispense", "tools_solderpaste_z_stop", "tools_solderpaste_z_travel",
- "tools_solderpaste_z_toolchange", "tools_solderpaste_xy_toolchange", "tools_solderpaste_frxy",
- "tools_solderpaste_frz", "tools_solderpaste_frz_dispense",
- 'global_gridx', 'global_gridy', 'global_snap_max', "global_tolerance"]
- def scale_options(sfactor):
- for dim in dimensions:
- if dim == 'excellon_toolchangexy':
- coordinates = self.defaults["excellon_toolchangexy"].split(",")
- coords_xy = [float(eval(a)) for a in coordinates if a != '']
- coords_xy[0] *= sfactor
- coords_xy[1] *= sfactor
- self.options['excellon_toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
- elif dim == 'geometry_toolchangexy':
- coordinates = self.defaults["geometry_toolchangexy"].split(",")
- coords_xy = [float(eval(a)) for a in coordinates if a != '']
- coords_xy[0] *= sfactor
- coords_xy[1] *= sfactor
- self.options['geometry_toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
- elif dim == 'geometry_cnctooldia':
- tools_diameters = []
- try:
- tools_string = self.defaults["geometry_cnctooldia"].split(",")
- tools_diameters = [eval(a) for a in tools_string if a != '']
- except Exception as e:
- log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
- self.options['geometry_cnctooldia'] = ''
- for t in range(len(tools_diameters)):
- tools_diameters[t] *= sfactor
- self.options['geometry_cnctooldia'] += "%f," % tools_diameters[t]
- elif dim == 'tools_ncctools':
- ncctools = []
- try:
- tools_string = self.defaults["tools_ncctools"].split(",")
- ncctools = [eval(a) for a in tools_string if a != '']
- except Exception as e:
- log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
- self.options['tools_ncctools'] = ''
- for t in range(len(ncctools)):
- ncctools[t] *= sfactor
- self.options['tools_ncctools'] += "%f," % ncctools[t]
- elif dim == 'tools_solderpaste_tools':
- sptools = []
- try:
- tools_string = self.defaults["tools_solderpaste_tools"].split(",")
- sptools = [eval(a) for a in tools_string if a != '']
- except Exception as e:
- log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
- self.options['tools_solderpaste_tools'] = ""
- for t in range(len(sptools)):
- sptools[t] *= sfactor
- self.options['tools_solderpaste_tools'] += "%f," % sptools[t]
- elif dim == 'tools_solderpaste_xy_toolchange':
- coordinates = self.defaults["tools_solderpaste_xy_toolchange"].split(",")
- sp_coords = [float(eval(a)) for a in coordinates if a != '']
- sp_coords[0] *= sfactor
- sp_coords[1] *= sfactor
- self.options['tools_solderpaste_xy_toolchange'] = "%f, %f" % (sp_coords[0], sp_coords[1])
- elif dim == 'global_gridx' or dim == 'global_gridy':
- if new_units == 'IN':
- val = 0.1
- try:
- val = float(self.defaults[dim]) * sfactor
- except Exception as e:
- log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
- self.options[dim] = float('%.6f' % val)
- else:
- val = 0.1
- try:
- val = float(self.defaults[dim]) * sfactor
- except Exception as e:
- log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
- self.options[dim] = float('%.4f' % val)
- else:
- val = 0.1
- try:
- val = float(self.options[dim]) * sfactor
- except Exception as e:
- log.debug('App.on_toggle_units().scale_options() --> %s' % str(e))
- self.options[dim] = val
- def scale_defaults(sfactor):
- for dim in dimensions:
- if dim == 'excellon_toolchangexy':
- coordinates = self.defaults["excellon_toolchangexy"].split(",")
- coords_xy = [float(eval(a)) for a in coordinates if a != '']
- coords_xy[0] *= sfactor
- coords_xy[1] *= sfactor
- self.defaults['excellon_toolchangexy'] = "%.4f, %.4f" % (coords_xy[0], coords_xy[1])
- elif dim == 'geometry_toolchangexy':
- coordinates = self.defaults["geometry_toolchangexy"].split(",")
- coords_xy = [float(eval(a)) for a in coordinates if a != '']
- coords_xy[0] *= sfactor
- coords_xy[1] *= sfactor
- self.defaults['geometry_toolchangexy'] = "%.4f, %.4f" % (coords_xy[0], coords_xy[1])
- elif dim == 'geometry_cnctooldia':
- tools_diameters = []
- try:
- tools_string = self.defaults["geometry_cnctooldia"].split(",")
- tools_diameters = [eval(a) for a in tools_string if a != '']
- except Exception as e:
- log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
- self.defaults['geometry_cnctooldia'] = ''
- for t in range(len(tools_diameters)):
- tools_diameters[t] *= sfactor
- self.defaults['geometry_cnctooldia'] += "%.4f," % tools_diameters[t]
- elif dim == 'tools_ncctools':
- ncctools = []
- try:
- tools_string = self.defaults["tools_ncctools"].split(",")
- ncctools = [eval(a) for a in tools_string if a != '']
- except Exception as e:
- log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
- self.defaults['tools_ncctools'] = ''
- for t in range(len(ncctools)):
- ncctools[t] *= sfactor
- self.defaults['tools_ncctools'] += "%.4f," % ncctools[t]
- elif dim == 'tools_solderpaste_tools':
- sptools = []
- try:
- tools_string = self.defaults["tools_solderpaste_tools"].split(",")
- sptools = [eval(a) for a in tools_string if a != '']
- except Exception as e:
- log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
- self.defaults['tools_solderpaste_tools'] = ""
- for t in range(len(sptools)):
- sptools[t] *= sfactor
- self.defaults['tools_solderpaste_tools'] += "%.4f," % sptools[t]
- elif dim == 'tools_solderpaste_xy_toolchange':
- coordinates = self.defaults["tools_solderpaste_xy_toolchange"].split(",")
- sp_coords = [float(eval(a)) for a in coordinates if a != '']
- sp_coords[0] *= sfactor
- sp_coords[1] *= sfactor
- self.defaults['tools_solderpaste_xy_toolchange'] = "%.4f, %.4f" % (sp_coords[0], sp_coords[1])
- elif dim == 'global_gridx' or dim == 'global_gridy':
- if new_units == 'IN':
- val = 0.1
- try:
- val = float(self.defaults[dim]) * sfactor
- except Exception as e:
- log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
- self.defaults[dim] = float('%.6f' % val)
- else:
- val = 0.1
- try:
- val = float(self.defaults[dim]) * sfactor
- except Exception as e:
- log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
- self.defaults[dim] = float('%.4f' % val)
- else:
- val = 0.1
- try:
- val = float(self.defaults[dim]) * sfactor
- except Exception as e:
- log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
- self.defaults[dim] = val
- # The scaling factor depending on choice of units.
- factor = 1/25.4
- if new_units == 'MM':
- factor = 25.4
- # Changing project units. Warn user.
- msgbox = QtWidgets.QMessageBox()
- msgbox.setWindowTitle(_("Toggle Units"))
- msgbox.setWindowIcon(QtGui.QIcon('share/toggle_units32.png'))
- msgbox.setText("<B>%s</B>" % _("Change project units ..."))
- msgbox.setInformativeText(_("Changing the units of the project causes all geometrical "
- "properties of all objects to be scaled accordingly.\nContinue?"))
- bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
- bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.RejectRole)
- msgbox.setDefaultButton(bt_ok)
- msgbox.exec_()
- response = msgbox.clickedButton()
- if response == bt_ok:
- if no_pref is False:
- self.options_read_form()
- scale_options(factor)
- self.options_write_form()
- self.defaults_read_form()
- scale_defaults(factor)
- self.defaults_write_form(fl_units=new_units)
- # save the defaults to file, some may assume that the conversion is enough and it's not
- self.on_save_button()
- self.should_we_save = True
- # change this only if the workspace is active
- if self.defaults['global_workspace'] is True:
- self.plotcanvas.draw_workspace()
- # adjust the grid values on the main toolbar
- dec = 6 if new_units == 'IN'else 4
- val_x = float(self.ui.grid_gap_x_entry.get_value()) * factor
- self.ui.grid_gap_x_entry.set_value(val_x, decimals=dec)
- if not self.ui.grid_gap_link_cb.isChecked():
- val_y = float(self.ui.grid_gap_y_entry.get_value()) * factor
- self.ui.grid_gap_y_entry.set_value(val_y, decimals=dec)
- for obj in self.collection.get_list():
- obj.convert_units(new_units)
- # make that the properties stored in the object are also updated
- self.object_changed.emit(obj)
- obj.build_ui()
- current = self.collection.get_active()
- if current is not None:
- # the transfer of converted values to the UI form for Geometry is done local in the FlatCAMObj.py
- if not isinstance(current, FlatCAMGeometry):
- current.to_form()
- self.plot_all()
- self.inform.emit('[success] %s: %s' %
- (_("Converted units to"), new_units))
- # self.ui.units_label.setText("[" + self.options["units"] + "]")
- self.set_screen_units(new_units)
- else:
- # Undo toggling
- self.toggle_units_ignore = True
- if self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
- self.ui.general_defaults_form.general_app_group.units_radio.set_value('IN')
- else:
- self.ui.general_defaults_form.general_app_group.units_radio.set_value('MM')
- self.toggle_units_ignore = False
- self.inform.emit('[WARNING_NOTCL]%s' %
- _(" Units conversion cancelled."))
- self.options_read_form()
- self.defaults_read_form()
- def on_toggle_units_click(self):
- try:
- self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.disconnect()
- except (TypeError, AttributeError):
- pass
- if self.defaults["units"] == 'MM':
- self.ui.general_defaults_form.general_app_group.units_radio.set_value("IN")
- else:
- self.ui.general_defaults_form.general_app_group.units_radio.set_value("MM")
- self.on_toggle_units(no_pref=True)
- self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(
- lambda: self.on_toggle_units(no_pref=False))
- def on_fullscreen(self, disable=False):
- self.report_usage("on_fullscreen()")
- if self.toggle_fscreen is False and disable is False:
- # self.ui.showFullScreen()
- self.ui.setWindowFlags(self.ui.windowFlags() | Qt.FramelessWindowHint)
- a = self.ui.geometry()
- self.x_pos = a.x()
- self.y_pos = a.y()
- self.width = a.width()
- self.height = a.height()
- # set new geometry to full desktop rect
- # Subtracting and adding the pixels below it's hack to bypass a bug in Qt5 and OpenGL that made that a
- # window drawn with OpenGL in fullscreen will not show any other windows on top which means that menus and
- # everything else will not work without this hack. This happen in Windows.
- # https://bugreports.qt.io/browse/QTBUG-41309
- desktop = QtWidgets.QApplication.desktop()
- screen = desktop.screenNumber(QtGui.QCursor.pos())
- rec = desktop.screenGeometry(screen)
- x = rec.x() - 1
- y = rec.y() - 1
- h = rec.height() + 2
- w = rec.width() + 2
- self.ui.setGeometry(x, y, w, h)
- self.ui.show()
- for tb in self.ui.findChildren(QtWidgets.QToolBar):
- tb.setVisible(False)
- self.ui.splitter_left.setVisible(False)
- self.toggle_fscreen = True
- elif self.toggle_fscreen is True or disable is True:
- self.ui.setWindowFlags(self.ui.windowFlags() & ~Qt.FramelessWindowHint)
- self.ui.setGeometry(self.x_pos, self.y_pos, self.width, self.height)
- self.ui.showNormal()
- self.restore_toolbar_view()
- self.ui.splitter_left.setVisible(True)
- self.toggle_fscreen = False
- def on_toggle_plotarea(self):
- self.report_usage("on_toggle_plotarea()")
- try:
- name = self.ui.plot_tab_area.widget(0).objectName()
- except AttributeError:
- self.ui.plot_tab_area.addTab(self.ui.plot_tab, "Plot Area")
- # remove the close button from the Plot Area tab (first tab index = 0) as this one will always be ON
- self.ui.plot_tab_area.protectTab(0)
- return
- if name != 'plotarea':
- self.ui.plot_tab_area.insertTab(0, self.ui.plot_tab, "Plot Area")
- # remove the close button from the Plot Area tab (first tab index = 0) as this one will always be ON
- self.ui.plot_tab_area.protectTab(0)
- else:
- self.ui.plot_tab_area.closeTab(0)
- def on_toggle_notebook(self):
- if self.ui.splitter.sizes()[0] == 0:
- self.ui.splitter.setSizes([1, 1])
- self.ui.menu_toggle_nb.setChecked(True)
- else:
- self.ui.splitter.setSizes([0, 1])
- self.ui.menu_toggle_nb.setChecked(False)
- def on_toggle_axis(self):
- self.report_usage("on_toggle_axis()")
- if self.toggle_axis is False:
- if self.is_legacy is False:
- # self.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
- # self.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
- self.plotcanvas.v_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=True,
- parent=self.plotcanvas.view.scene)
- self.plotcanvas.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=False,
- parent=self.plotcanvas.view.scene)
- # self.plotcanvas.redraw()
- else:
- self.plotcanvas.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
- self.plotcanvas.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
- self.plotcanvas.canvas.draw()
- pass
- self.toggle_axis = True
- else:
- if self.is_legacy is False:
- # self.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
- # self.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
- # self.plotcanvas.redraw()
- self.plotcanvas.v_line.parent = None
- self.plotcanvas.h_line.parent = None
- else:
- self.plotcanvas.axes.lines[:] = []
- self.plotcanvas.canvas.draw()
- self.toggle_axis = False
- def on_toggle_grid(self):
- self.report_usage("on_toggle_grid()")
- self.ui.grid_snap_btn.trigger()
- def on_options_combo_change(self, sel):
- """
- Called when the combo box to choose between application defaults and
- project option changes value. The corresponding variables are
- copied to the UI.
- :param sel: The option index that was chosen.
- :return: None
- """
- # combo_sel = self.ui.notebook.combo_options.get_active()
- App.log.debug("Options --> %s" % sel)
- # form = [self.defaults_form, self.options_form][sel]
- # self.ui.notebook.options_contents.pack_start(form, False, False, 1)
- if sel == 0:
- self.gen_form = self.ui.general_defaults_form
- self.ger_form = self.ui.gerber_defaults_form
- self.exc_form = self.ui.excellon_defaults_form
- self.geo_form = self.ui.geometry_defaults_form
- self.cnc_form = self.ui.cncjob_defaults_form
- self.tools_form = self.ui.tools_defaults_form
- self.fa_form = self.ui.util_defaults_form
- elif sel == 1:
- self.gen_form = self.ui.general_options_form
- self.ger_form = self.ui.gerber_options_form
- self.exc_form = self.ui.excellon_options_form
- self.geo_form = self.ui.geometry_options_form
- self.cnc_form = self.ui.cncjob_options_form
- self.tools_form = self.ui.tools_options_form
- self.fa_form = self.ui.util_options_form
- else:
- return
- try:
- self.ui.general_scroll_area.takeWidget()
- except:
- self.log.debug("Nothing to remove")
- self.ui.general_scroll_area.setWidget(self.gen_form)
- self.gen_form.show()
- try:
- self.ui.gerber_scroll_area.takeWidget()
- except:
- self.log.debug("Nothing to remove")
- self.ui.gerber_scroll_area.setWidget(self.ger_form)
- self.ger_form.show()
- try:
- self.ui.excellon_scroll_area.takeWidget()
- except:
- self.log.debug("Nothing to remove")
- self.ui.excellon_scroll_area.setWidget(self.exc_form)
- self.exc_form.show()
- try:
- self.ui.geometry_scroll_area.takeWidget()
- except:
- self.log.debug("Nothing to remove")
- self.ui.geometry_scroll_area.setWidget(self.geo_form)
- self.geo_form.show()
- try:
- self.ui.cncjob_scroll_area.takeWidget()
- except:
- self.log.debug("Nothing to remove")
- self.ui.cncjob_scroll_area.setWidget(self.cnc_form)
- self.cnc_form.show()
- try:
- self.ui.tools_scroll_area.takeWidget()
- except:
- self.log.debug("Nothing to remove")
- self.ui.tools_scroll_area.setWidget(self.tools_form)
- self.tools_form.show()
- try:
- self.ui.fa_scroll_area.takeWidget()
- except:
- self.log.debug("Nothing to remove")
- self.ui.fa_scroll_area.setWidget(self.fa_form)
- self.fa_form.show()
- self.log.debug("Finished GUI form initialization.")
- # self.options2form()
- def on_excellon_defaults_button(self):
- self.defaults_form_fields["excellon_format_lower_in"].set_value('4')
- self.defaults_form_fields["excellon_format_upper_in"].set_value('2')
- self.defaults_form_fields["excellon_format_lower_mm"].set_value('3')
- self.defaults_form_fields["excellon_format_upper_mm"].set_value('3')
- self.defaults_form_fields["excellon_zeros"].set_value('L')
- self.defaults_form_fields["excellon_units"].set_value('INCH')
- log.debug("Excellon app defaults loaded ...")
- def on_excellon_options_button(self):
- self.options_form_fields["excellon_format_lower_in"].set_value('4')
- self.options_form_fields["excellon_format_upper_in"].set_value('2')
- self.options_form_fields["excellon_format_lower_mm"].set_value('3')
- self.options_form_fields["excellon_format_upper_mm"].set_value('3')
- self.options_form_fields["excellon_zeros"].set_value('L')
- self.options_form_fields["excellon_units"].set_value('INCH')
- log.debug("Excellon options defaults loaded ...")
- def on_update_exc_export(self, state):
- """
- This is handling the update of Excellon Export parameters based on the ones in the Excellon General but only
- if the update_excellon_cb checkbox is checked
- :param state: state of the checkbox whose signals is tied to his slot
- :return:
- """
- if state:
- # first try to disconnect
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry.textChanged.\
- disconnect(self.on_excellon_format_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_in_entry.textChanged.\
- disconnect(self.on_excellon_format_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_mm_entry.textChanged.\
- disconnect(self.on_excellon_format_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_mm_entry.textChanged.\
- disconnect(self.on_excellon_format_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_zeros_radio.activated_custom.\
- disconnect(self.on_excellon_zeros_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_units_radio.activated_custom.\
- disconnect(self.on_excellon_zeros_changed)
- except TypeError:
- pass
- # the connect them
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry.textChanged.connect(
- self.on_excellon_format_changed)
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_in_entry.textChanged.connect(
- self.on_excellon_format_changed)
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_mm_entry.textChanged.connect(
- self.on_excellon_format_changed)
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_mm_entry.textChanged.connect(
- self.on_excellon_format_changed)
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_zeros_radio.activated_custom.connect(
- self.on_excellon_zeros_changed)
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_units_radio.activated_custom.connect(
- self.on_excellon_units_changed)
- else:
- # disconnect the signals
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry.textChanged. \
- disconnect(self.on_excellon_format_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_in_entry.textChanged. \
- disconnect(self.on_excellon_format_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_mm_entry.textChanged. \
- disconnect(self.on_excellon_format_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_mm_entry.textChanged. \
- disconnect(self.on_excellon_format_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_zeros_radio.activated_custom. \
- disconnect(self.on_excellon_zeros_changed)
- except TypeError:
- pass
- try:
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_units_radio.activated_custom. \
- disconnect(self.on_excellon_zeros_changed)
- except TypeError:
- pass
- def on_excellon_format_changed(self):
- """
- Slot activated when the user changes the Excellon format values in Preferences -> Excellon -> Excellon General
- :return: None
- """
- if self.ui.excellon_defaults_form.excellon_gen_group.excellon_units_radio.get_value().upper() == 'METRIC':
- self.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_mm_entry.get_value()
- )
- self.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_mm_entry.get_value()
- )
- else:
- self.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry.get_value()
- )
- self.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_in_entry.get_value()
- )
- def on_excellon_zeros_changed(self):
- """
- Slot activated when the user changes the Excellon zeros values in Preferences -> Excellon -> Excellon General
- :return: None
- """
- self.ui.excellon_defaults_form.excellon_exp_group.zeros_radio.set_value(
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_zeros_radio.get_value() + 'Z'
- )
- def on_excellon_units_changed(self):
- """
- Slot activated when the user changes the Excellon unit values in Preferences -> Excellon -> Excellon General
- :return: None
- """
- self.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio.set_value(
- self.ui.excellon_defaults_form.excellon_gen_group.excellon_units_radio.get_value()
- )
- self.on_excellon_format_changed()
- # Setting plot colors handlers
- def on_pf_color_entry(self):
- self.defaults['global_plot_fill'] = \
- self.ui.general_defaults_form.general_gui_group.pf_color_entry.get_value()[:7] + \
- self.defaults['global_plot_fill'][7:9]
- self.ui.general_defaults_form.general_gui_group.pf_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_plot_fill'])[:7])
- def on_pf_color_button(self):
- current_color = QtGui.QColor(self.defaults['global_plot_fill'][:7])
- c_dialog = QtWidgets.QColorDialog()
- plot_fill_color = c_dialog.getColor(initial=current_color)
- if plot_fill_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.pf_color_button.setStyleSheet(
- "background-color:%s" % str(plot_fill_color.name()))
- new_val = str(plot_fill_color.name()) + str(self.defaults['global_plot_fill'][7:9])
- self.ui.general_defaults_form.general_gui_group.pf_color_entry.set_value(new_val)
- self.defaults['global_plot_fill'] = new_val
- def on_pf_color_spinner(self):
- spinner_value = self.ui.general_defaults_form.general_gui_group.pf_color_alpha_spinner.value()
- self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.setValue(spinner_value)
- self.defaults['global_plot_fill'] = \
- self.defaults['global_plot_fill'][:7] + \
- (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
- self.defaults['global_plot_line'] = \
- self.defaults['global_plot_line'][:7] + \
- (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
- def on_pf_color_slider(self):
- slider_value = self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value()
- self.ui.general_defaults_form.general_gui_group.pf_color_alpha_spinner.setValue(slider_value)
- def on_pl_color_entry(self):
- self.defaults['global_plot_line'] = \
- self.ui.general_defaults_form.general_gui_group.pl_color_entry.get_value()[:7] + \
- self.defaults['global_plot_line'][7:9]
- self.ui.general_defaults_form.general_gui_group.pl_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_plot_line'])[:7])
- def on_pl_color_button(self):
- current_color = QtGui.QColor(self.defaults['global_plot_line'][:7])
- # print(current_color)
- c_dialog = QtWidgets.QColorDialog()
- plot_line_color = c_dialog.getColor(initial=current_color)
- if plot_line_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.pl_color_button.setStyleSheet(
- "background-color:%s" % str(plot_line_color.name()))
- new_val_line = str(plot_line_color.name()) + str(self.defaults['global_plot_line'][7:9])
- self.ui.general_defaults_form.general_gui_group.pl_color_entry.set_value(new_val_line)
- self.defaults['global_plot_line'] = new_val_line
- # Setting selection colors (left - right) handlers
- def on_sf_color_entry(self):
- self.defaults['global_sel_fill'] = \
- self.ui.general_defaults_form.general_gui_group.sf_color_entry.get_value()[:7] + \
- self.defaults['global_sel_fill'][7:9]
- self.ui.general_defaults_form.general_gui_group.sf_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_sel_fill'])[:7])
- def on_sf_color_button(self):
- current_color = QtGui.QColor(self.defaults['global_sel_fill'][:7])
- c_dialog = QtWidgets.QColorDialog()
- plot_fill_color = c_dialog.getColor(initial=current_color)
- if plot_fill_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.sf_color_button.setStyleSheet(
- "background-color:%s" % str(plot_fill_color.name()))
- new_val = str(plot_fill_color.name()) + str(self.defaults['global_sel_fill'][7:9])
- self.ui.general_defaults_form.general_gui_group.sf_color_entry.set_value(new_val)
- self.defaults['global_sel_fill'] = new_val
- def on_sf_color_spinner(self):
- spinner_value = self.ui.general_defaults_form.general_gui_group.sf_color_alpha_spinner.value()
- self.ui.general_defaults_form.general_gui_group.sf_color_alpha_slider.setValue(spinner_value)
- self.defaults['global_sel_fill'] = \
- self.defaults['global_sel_fill'][:7] + \
- (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
- self.defaults['global_sel_line'] = \
- self.defaults['global_sel_line'][:7] + \
- (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
- def on_sf_color_slider(self):
- slider_value = self.ui.general_defaults_form.general_gui_group.sf_color_alpha_slider.value()
- self.ui.general_defaults_form.general_gui_group.sf_color_alpha_spinner.setValue(slider_value)
- def on_sl_color_entry(self):
- self.defaults['global_sel_line'] = \
- self.ui.general_defaults_form.general_gui_group.sl_color_entry.get_value()[:7] + \
- self.defaults['global_sel_line'][7:9]
- self.ui.general_defaults_form.general_gui_group.sl_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_sel_line'])[:7])
- def on_sl_color_button(self):
- current_color = QtGui.QColor(self.defaults['global_sel_line'][:7])
- c_dialog = QtWidgets.QColorDialog()
- plot_line_color = c_dialog.getColor(initial=current_color)
- if plot_line_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.sl_color_button.setStyleSheet(
- "background-color:%s" % str(plot_line_color.name()))
- new_val_line = str(plot_line_color.name()) + str(self.defaults['global_sel_line'][7:9])
- self.ui.general_defaults_form.general_gui_group.sl_color_entry.set_value(new_val_line)
- self.defaults['global_sel_line'] = new_val_line
- # Setting selection colors (right - left) handlers
- def on_alt_sf_color_entry(self):
- self.defaults['global_alt_sel_fill'] = self.ui.general_defaults_form.general_gui_group \
- .alt_sf_color_entry.get_value()[:7] + self.defaults['global_alt_sel_fill'][7:9]
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_alt_sel_fill'])[:7])
- def on_alt_sf_color_button(self):
- current_color = QtGui.QColor(self.defaults['global_alt_sel_fill'][:7])
- c_dialog = QtWidgets.QColorDialog()
- plot_fill_color = c_dialog.getColor(initial=current_color)
- if plot_fill_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.setStyleSheet(
- "background-color:%s" % str(plot_fill_color.name()))
- new_val = str(plot_fill_color.name()) + str(self.defaults['global_alt_sel_fill'][7:9])
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.set_value(new_val)
- self.defaults['global_alt_sel_fill'] = new_val
- def on_alt_sf_color_spinner(self):
- spinner_value = self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_spinner.value()
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_slider.setValue(spinner_value)
- self.defaults['global_alt_sel_fill'] = \
- self.defaults['global_alt_sel_fill'][:7] + \
- (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
- self.defaults['global_alt_sel_line'] = \
- self.defaults['global_alt_sel_line'][:7] + \
- (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
- def on_alt_sf_color_slider(self):
- slider_value = self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_slider.value()
- self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_spinner.setValue(slider_value)
- def on_alt_sl_color_entry(self):
- self.defaults['global_alt_sel_line'] = \
- self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.get_value()[:7] + \
- self.defaults['global_alt_sel_line'][7:9]
- self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_alt_sel_line'])[:7])
- def on_alt_sl_color_button(self):
- current_color = QtGui.QColor(self.defaults['global_alt_sel_line'][:7])
- c_dialog = QtWidgets.QColorDialog()
- plot_line_color = c_dialog.getColor(initial=current_color)
- if plot_line_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.setStyleSheet(
- "background-color:%s" % str(plot_line_color.name()))
- new_val_line = str(plot_line_color.name()) + str(self.defaults['global_alt_sel_line'][7:9])
- self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.set_value(new_val_line)
- self.defaults['global_alt_sel_line'] = new_val_line
- # Setting Editor colors
- def on_draw_color_entry(self):
- self.defaults['global_draw_color'] = self.ui.general_defaults_form.general_gui_group \
- .draw_color_entry.get_value()
- self.ui.general_defaults_form.general_gui_group.draw_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_draw_color']))
- def on_draw_color_button(self):
- current_color = QtGui.QColor(self.defaults['global_draw_color'])
- c_dialog = QtWidgets.QColorDialog()
- draw_color = c_dialog.getColor(initial=current_color)
- if draw_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.draw_color_button.setStyleSheet(
- "background-color:%s" % str(draw_color.name()))
- new_val = str(draw_color.name())
- self.ui.general_defaults_form.general_gui_group.draw_color_entry.set_value(new_val)
- self.defaults['global_draw_color'] = new_val
- def on_sel_draw_color_entry(self):
- self.defaults['global_sel_draw_color'] = self.ui.general_defaults_form.general_gui_group \
- .sel_draw_color_entry.get_value()
- self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_sel_draw_color']))
- def on_sel_draw_color_button(self):
- current_color = QtGui.QColor(self.defaults['global_sel_draw_color'])
- c_dialog = QtWidgets.QColorDialog()
- sel_draw_color = c_dialog.getColor(initial=current_color)
- if sel_draw_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.setStyleSheet(
- "background-color:%s" % str(sel_draw_color.name()))
- new_val_sel = str(sel_draw_color.name())
- self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.set_value(new_val_sel)
- self.defaults['global_sel_draw_color'] = new_val_sel
- def on_proj_color_entry(self):
- self.defaults['global_proj_item_color'] = self.ui.general_defaults_form.general_gui_group \
- .proj_color_entry.get_value()
- self.ui.general_defaults_form.general_gui_group.proj_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_proj_item_color']))
- def on_proj_color_button(self):
- current_color = QtGui.QColor(self.defaults['global_proj_item_color'])
- c_dialog = QtWidgets.QColorDialog()
- proj_color = c_dialog.getColor(initial=current_color)
- if proj_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.proj_color_button.setStyleSheet(
- "background-color:%s" % str(proj_color.name()))
- new_val_sel = str(proj_color.name())
- self.ui.general_defaults_form.general_gui_group.proj_color_entry.set_value(new_val_sel)
- self.defaults['global_proj_item_color'] = new_val_sel
- def on_proj_color_dis_entry(self):
- self.defaults['global_proj_item_dis_color'] = self.ui.general_defaults_form.general_gui_group \
- .proj_color_dis_entry.get_value()
- self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['global_proj_item_dis_color']))
- def on_proj_color_dis_button(self):
- current_color = QtGui.QColor(self.defaults['global_proj_item_dis_color'])
- c_dialog = QtWidgets.QColorDialog()
- proj_color = c_dialog.getColor(initial=current_color)
- if proj_color.isValid() is False:
- return
- self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.setStyleSheet(
- "background-color:%s" % str(proj_color.name()))
- new_val_sel = str(proj_color.name())
- self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry.set_value(new_val_sel)
- self.defaults['global_proj_item_dis_color'] = new_val_sel
- def on_annotation_fontcolor_entry(self):
- self.defaults['cncjob_annotation_fontcolor'] = \
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry.get_value()
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['cncjob_annotation_fontcolor']))
- def on_annotation_fontcolor_button(self):
- current_color = QtGui.QColor(self.defaults['cncjob_annotation_fontcolor'])
- c_dialog = QtWidgets.QColorDialog()
- annotation_color = c_dialog.getColor(initial=current_color)
- if annotation_color.isValid() is False:
- return
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_button.setStyleSheet(
- "background-color:%s" % str(annotation_color.name()))
- new_val_sel = str(annotation_color.name())
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry.set_value(new_val_sel)
- self.defaults['cncjob_annotation_fontcolor'] = new_val_sel
- def on_film_color_entry(self):
- self.defaults['tools_film_color'] = \
- self.ui.tools_defaults_form.tools_film_group.film_color_entry.get_value()
- self.ui.tools_defaults_form.tools_film_group.film_color_button.setStyleSheet(
- "background-color:%s" % str(self.defaults['tools_film_color']))
- def on_film_color_button(self):
- current_color = QtGui.QColor(self.defaults['tools_film_color'])
- c_dialog = QtWidgets.QColorDialog()
- film_color = c_dialog.getColor(initial=current_color)
- if film_color.isValid() is False:
- return
- # if new color is different then mark that the Preferences are changed
- if film_color != current_color:
- self.on_preferences_edited()
- self.ui.tools_defaults_form.tools_film_group.film_color_button.setStyleSheet(
- "background-color:%s" % str(film_color.name()))
- new_val_sel = str(film_color.name())
- self.ui.tools_defaults_form.tools_film_group.film_color_entry.set_value(new_val_sel)
- self.defaults['tools_film_color'] = new_val_sel
- def on_splash_changed(self, state):
- settings = QSettings("Open Source", "FlatCAM")
- settings.setValue('splash_screen', 1) if state else settings.setValue('splash_screen', 0)
- # This will write the setting to the platform specific storage.
- del settings
- def on_notebook_tab_rmb_click(self, checked):
- self.ui.notebook.set_detachable(val=checked)
- self.defaults["global_tabs_detachable"] = checked
- self.ui.plot_tab_area.set_detachable(val=checked)
- self.defaults["global_tabs_detachable"] = checked
- def on_deselect_all(self):
- self.collection.set_all_inactive()
- self.delete_selection_shape()
- def on_workspace_modified(self):
- self.save_defaults(silent=True)
- self.plotcanvas.draw_workspace()
- def on_workspace(self):
- self.report_usage("on_workspace()")
- if self.ui.general_defaults_form.general_gui_group.workspace_cb.isChecked():
- self.plotcanvas.restore_workspace()
- else:
- self.plotcanvas.delete_workspace()
- self.save_defaults(silent=True)
- def on_workspace_menu(self):
- if self.ui.general_defaults_form.general_gui_group.workspace_cb.isChecked():
- self.ui.general_defaults_form.general_gui_group.workspace_cb.setChecked(False)
- else:
- self.ui.general_defaults_form.general_gui_group.workspace_cb.setChecked(True)
- self.on_workspace()
- def on_layout(self, index=None, lay=None):
- """
- Set the toolbars layout (location)
- :param index:
- :param lay: type of layout to be set on the toolbard
- :return: None
- """
- self.report_usage("on_layout()")
- if lay:
- current_layout = lay
- else:
- current_layout = self.ui.general_defaults_form.general_gui_set_group.layout_combo.get_value()
- settings = QSettings("Open Source", "FlatCAM")
- settings.setValue('layout', current_layout)
- # This will write the setting to the platform specific storage.
- del settings
- # first remove the toolbars:
- try:
- self.ui.removeToolBar(self.ui.toolbarfile)
- self.ui.removeToolBar(self.ui.toolbargeo)
- self.ui.removeToolBar(self.ui.toolbarview)
- self.ui.removeToolBar(self.ui.toolbarshell)
- self.ui.removeToolBar(self.ui.toolbartools)
- self.ui.removeToolBar(self.ui.exc_edit_toolbar)
- self.ui.removeToolBar(self.ui.geo_edit_toolbar)
- self.ui.removeToolBar(self.ui.grb_edit_toolbar)
- self.ui.removeToolBar(self.ui.snap_toolbar)
- self.ui.removeToolBar(self.ui.toolbarshell)
- except Exception as e:
- pass
- if current_layout == 'standard':
- # ## TOOLBAR INSTALLATION # ##
- self.ui.toolbarfile = QtWidgets.QToolBar('File Toolbar')
- self.ui.toolbarfile.setObjectName('File_TB')
- self.ui.addToolBar(self.ui.toolbarfile)
- self.ui.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
- self.ui.toolbargeo.setObjectName('Edit_TB')
- self.ui.addToolBar(self.ui.toolbargeo)
- self.ui.toolbarview = QtWidgets.QToolBar('View Toolbar')
- self.ui.toolbarview.setObjectName('View_TB')
- self.ui.addToolBar(self.ui.toolbarview)
- self.ui.toolbarshell = QtWidgets.QToolBar('Shell Toolbar')
- self.ui.toolbarshell.setObjectName('Shell_TB')
- self.ui.addToolBar(self.ui.toolbarshell)
- self.ui.toolbartools = QtWidgets.QToolBar('Tools Toolbar')
- self.ui.toolbartools.setObjectName('Tools_TB')
- self.ui.addToolBar(self.ui.toolbartools)
- self.ui.exc_edit_toolbar = QtWidgets.QToolBar('Excellon Editor Toolbar')
- # self.ui.exc_edit_toolbar.setVisible(False)
- self.ui.exc_edit_toolbar.setObjectName('ExcEditor_TB')
- self.ui.addToolBar(self.ui.exc_edit_toolbar)
- self.ui.addToolBarBreak()
- self.ui.geo_edit_toolbar = QtWidgets.QToolBar('Geometry Editor Toolbar')
- # self.ui.geo_edit_toolbar.setVisible(False)
- self.ui.geo_edit_toolbar.setObjectName('GeoEditor_TB')
- self.ui.addToolBar(self.ui.geo_edit_toolbar)
- self.ui.grb_edit_toolbar = QtWidgets.QToolBar('Gerber Editor Toolbar')
- # self.ui.grb_edit_toolbar.setVisible(False)
- self.ui.grb_edit_toolbar.setObjectName('GrbEditor_TB')
- self.ui.addToolBar(self.ui.grb_edit_toolbar)
- self.ui.snap_toolbar = QtWidgets.QToolBar('Grid Toolbar')
- self.ui.snap_toolbar.setObjectName('Snap_TB')
- # self.ui.snap_toolbar.setMaximumHeight(30)
- self.ui.addToolBar(self.ui.snap_toolbar)
- self.ui.corner_snap_btn.setVisible(False)
- self.ui.snap_magnet.setVisible(False)
- elif current_layout == 'compact':
- # ## TOOLBAR INSTALLATION # ##
- self.ui.toolbarfile = QtWidgets.QToolBar('File Toolbar')
- self.ui.toolbarfile.setObjectName('File_TB')
- self.ui.addToolBar(Qt.LeftToolBarArea, self.ui.toolbarfile)
- self.ui.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
- self.ui.toolbargeo.setObjectName('Edit_TB')
- self.ui.addToolBar(Qt.LeftToolBarArea, self.ui.toolbargeo)
- self.ui.toolbarshell = QtWidgets.QToolBar('Shell Toolbar')
- self.ui.toolbarshell.setObjectName('Shell_TB')
- self.ui.addToolBar(Qt.LeftToolBarArea, self.ui.toolbarshell)
- self.ui.toolbartools = QtWidgets.QToolBar('Tools Toolbar')
- self.ui.toolbartools.setObjectName('Tools_TB')
- self.ui.addToolBar(Qt.LeftToolBarArea, self.ui.toolbartools)
- self.ui.geo_edit_toolbar = QtWidgets.QToolBar('Geometry Editor Toolbar')
- # self.ui.geo_edit_toolbar.setVisible(False)
- self.ui.geo_edit_toolbar.setObjectName('GeoEditor_TB')
- self.ui.addToolBar(Qt.RightToolBarArea, self.ui.geo_edit_toolbar)
- self.ui.toolbarview = QtWidgets.QToolBar('View Toolbar')
- self.ui.toolbarview.setObjectName('View_TB')
- self.ui.addToolBar(Qt.RightToolBarArea, self.ui.toolbarview)
- self.ui.addToolBarBreak(area=Qt.RightToolBarArea)
- self.ui.grb_edit_toolbar = QtWidgets.QToolBar('Gerber Editor Toolbar')
- # self.ui.grb_edit_toolbar.setVisible(False)
- self.ui.grb_edit_toolbar.setObjectName('GrbEditor_TB')
- self.ui.addToolBar(Qt.RightToolBarArea, self.ui.grb_edit_toolbar)
- self.ui.exc_edit_toolbar = QtWidgets.QToolBar('Excellon Editor Toolbar')
- self.ui.exc_edit_toolbar.setObjectName('ExcEditor_TB')
- self.ui.addToolBar(Qt.RightToolBarArea, self.ui.exc_edit_toolbar)
- self.ui.snap_toolbar = QtWidgets.QToolBar('Grid Toolbar')
- self.ui.snap_toolbar.setObjectName('Snap_TB')
- self.ui.snap_toolbar.setMaximumHeight(30)
- self.ui.splitter_left.addWidget(self.ui.snap_toolbar)
- self.ui.corner_snap_btn.setVisible(True)
- self.ui.snap_magnet.setVisible(True)
- # add all the actions to the toolbars
- self.ui.populate_toolbars()
- # reconnect all the signals to the toolbar actions
- self.connect_toolbar_signals()
- self.ui.grid_snap_btn.setChecked(True)
- self.ui.grid_gap_x_entry.setText(str(self.defaults["global_gridx"]))
- self.ui.grid_gap_y_entry.setText(str(self.defaults["global_gridy"]))
- self.ui.snap_max_dist_entry.setText(str(self.defaults["global_snap_max"]))
- self.ui.grid_gap_link_cb.setChecked(True)
- def on_cursor_type(self, val):
- """
- :param val: type of mouse cursor, set in Preferences ('small' or 'big')
- :return: None
- """
- self.app_cursor.enabled = False
- if val == 'small':
- self.ui.general_defaults_form.general_gui_set_group.cursor_size_entry.setDisabled(False)
- self.ui.general_defaults_form.general_gui_set_group.cursor_size_lbl.setDisabled(False)
- self.app_cursor = self.plotcanvas.new_cursor()
- else:
- self.ui.general_defaults_form.general_gui_set_group.cursor_size_entry.setDisabled(True)
- self.ui.general_defaults_form.general_gui_set_group.cursor_size_lbl.setDisabled(True)
- self.app_cursor = self.plotcanvas.new_cursor(big=True)
- if self.ui.grid_snap_btn.isChecked():
- self.app_cursor.enabled = True
- else:
- self.app_cursor.enabled = False
- def on_cnc_custom_parameters(self, signal_text):
- if signal_text == 'Parameters':
- return
- else:
- self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_text.insertPlainText('%%%s%%' % signal_text)
- def on_save_button(self):
- log.debug("App.on_save_button() --> Saving preferences to file.")
- self.preferences_changed_flag = False
- self.save_defaults(silent=False)
- # load the defaults so they are updated into the app
- self.load_defaults(filename='current_defaults')
- # Re-fresh project options
- self.on_options_app2project()
- settings = QSettings("Open Source", "FlatCAM")
- # save the notebook font size
- fsize = self.ui.general_defaults_form.general_gui_set_group.notebook_font_size_spinner.get_value()
- settings.setValue('notebook_font_size', fsize)
- # save the axis font size
- g_fsize = self.ui.general_defaults_form.general_gui_set_group.axis_font_size_spinner.get_value()
- settings.setValue('axis_font_size', g_fsize)
- # save the textbox font size
- tb_fsize = self.ui.general_defaults_form.general_gui_set_group.textbox_font_size_spinner.get_value()
- settings.setValue('textbox_font_size', tb_fsize)
- # This will write the setting to the platform specific storage.
- del settings
- def on_tool_add_keypress(self):
- # ## Current application units in Upper Case
- self.units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
- notebook_widget_name = self.ui.notebook.currentWidget().objectName()
- # work only if the notebook tab on focus is the Selected_Tab and only if the object is Geometry
- if notebook_widget_name == 'selected_tab':
- if str(type(self.collection.get_active())) == "<class 'FlatCAMObj.FlatCAMGeometry'>":
- # Tool add works for Geometry only if Advanced is True in Preferences
- if self.defaults["global_app_level"] == 'a':
- tool_add_popup = FCInputDialog(title="New Tool ...",
- text='Enter a Tool Diameter:',
- min=0.0000, max=99.9999, decimals=4)
- tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
- val, ok = tool_add_popup.get_value()
- if ok:
- if float(val) == 0:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Please enter a tool diameter with non-zero value, in Float format."))
- return
- self.collection.get_active().on_tool_add(dia=float(val))
- else:
- self.inform.emit('[WARNING_NOTCL] %s...' %
- _("Adding Tool cancelled"))
- else:
- msgbox = QtWidgets.QMessageBox()
- msgbox.setText(_("Adding Tool works only when Advanced is checked.\n"
- "Go to Preferences -> General - Show Advanced Options."))
- msgbox.setWindowTitle("Tool adding ...")
- msgbox.setWindowIcon(QtGui.QIcon('share/warning.png'))
- bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
- msgbox.setDefaultButton(bt_ok)
- msgbox.exec_()
- # work only if the notebook tab on focus is the Tools_Tab
- if notebook_widget_name == 'tool_tab':
- tool_widget = self.ui.tool_scroll_area.widget().objectName()
- # and only if the tool is NCC Tool
- if tool_widget == self.ncclear_tool.toolName:
- self.ncclear_tool.on_add_tool_by_key()
- # and only if the tool is Paint Area Tool
- elif tool_widget == self.paint_tool.toolName:
- self.paint_tool.on_add_tool_by_key()
- # and only if the tool is Solder Paste Dispensing Tool
- elif tool_widget == self.paste_tool.toolName:
- self.paste_tool.on_add_tool_by_key()
- # It's meant to delete tools in tool tables via a 'Delete' shortcut key but only if certain conditions are met
- # See description bellow.
- def on_delete_keypress(self):
- notebook_widget_name = self.ui.notebook.currentWidget().objectName()
- # work only if the notebook tab on focus is the Selected_Tab and only if the object is Geometry
- if notebook_widget_name == 'selected_tab':
- if str(type(self.collection.get_active())) == "<class 'FlatCAMObj.FlatCAMGeometry'>":
- self.collection.get_active().on_tool_delete()
- # work only if the notebook tab on focus is the Tools_Tab
- elif notebook_widget_name == 'tool_tab':
- tool_widget = self.ui.tool_scroll_area.widget().objectName()
- # and only if the tool is NCC Tool
- if tool_widget == self.ncclear_tool.toolName:
- self.ncclear_tool.on_tool_delete()
- # and only if the tool is Paint Tool
- elif tool_widget == self.paint_tool.toolName:
- self.paint_tool.on_tool_delete()
- # and only if the tool is Solder Paste Dispensing Tool
- elif tool_widget == self.paste_tool.toolName:
- self.paste_tool.on_tool_delete()
- else:
- self.on_delete()
- # It's meant to delete selected objects. It work also activated by a shortcut key 'Delete' same as above so in
- # some screens you have to be careful where you hover with your mouse.
- # Hovering over Selected tab, if the selected tab is a Geometry it will delete tools in tool table. But even if
- # there is a Selected tab in focus with a Geometry inside, if you hover over canvas it will delete an object.
- # Complicated, I know :)
- def on_delete(self):
- """
- Delete the currently selected FlatCAMObjs.
- :return: None
- """
- self.report_usage("on_delete()")
- response = None
- bt_ok = None
- # Make sure that the deletion will happen only after the Editor is no longer active otherwise we might delete
- # a geometry object before we update it.
- if self.geo_editor.editor_active is False and self.exc_editor.editor_active is False \
- and self.grb_editor.editor_active is False:
- if self.defaults["global_delete_confirmation"] is True:
- msgbox = QtWidgets.QMessageBox()
- msgbox.setWindowTitle(_("Delete objects"))
- msgbox.setWindowIcon(QtGui.QIcon('share/deleteshape32.png'))
- # msgbox.setText("<B>%s</B>" % _("Change project units ..."))
- msgbox.setText(_("Are you sure you want to permanently delete\n"
- "the selected objects?"))
- bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
- bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.RejectRole)
- msgbox.setDefaultButton(bt_ok)
- msgbox.exec_()
- response = msgbox.clickedButton()
- if response == bt_ok or self.defaults["global_delete_confirmation"] is False:
- if self.collection.get_active():
- self.log.debug("App.on_delete()")
- while self.collection.get_active():
- obj_active = self.collection.get_active()
- # if the deleted object is FlatCAMGerber then make sure to delete the possible mark shapes
- if isinstance(obj_active, FlatCAMGerber):
- for el in obj_active.mark_shapes:
- obj_active.mark_shapes[el].clear(update=True)
- obj_active.mark_shapes[el].enabled = False
- # obj_active.mark_shapes[el] = None
- del el
- elif isinstance(obj_active, FlatCAMCNCjob):
- try:
- obj_active.annotation.clear(update=True)
- obj_active.annotation.enabled = False
- except AttributeError:
- pass
- self.delete_first_selected()
- self.inform.emit('%s...' %
- _("Object(s) deleted"))
- # make sure that the selection shape is deleted, too
- self.delete_selection_shape()
- else:
- self.inform.emit(_("Failed. No object(s) selected..."))
- else:
- self.inform.emit(_("Save the work in Editor and try again ..."))
- def delete_first_selected(self):
- # Keep this for later
- try:
- sel_obj = self.collection.get_active()
- name = sel_obj.options["name"]
- isPlotted = sel_obj.options["plot"]
- except AttributeError:
- self.log.debug("Nothing selected for deletion")
- return
- if self.is_legacy is True:
- # Remove plot only if the object was plotted otherwise delaxes will fail
- if isPlotted:
- try:
- # self.plotcanvas.figure.delaxes(self.collection.get_active().axes)
- self.plotcanvas.figure.delaxes(self.collection.get_active().shapes.axes)
- except Exception as e:
- log.debug("App.delete_first_selected() --> %s" % str(e))
- self.plotcanvas.auto_adjust_axes()
- # Remove from dictionary
- self.collection.delete_active()
- # Clear form
- self.setup_component_editor()
- self.inform.emit('%s: %s' %
- (_("Object deleted"), name))
- def on_set_origin(self):
- """
- Set the origin to the left mouse click position
- :return: None
- """
- # display the message for the user
- # and ask him to click on the desired position
- self.report_usage("on_set_origin()")
- def origin_replot():
- def worker_task():
- with self.proc_container.new('%s...' % _("Plotting")):
- for obj in self.collection.get_list():
- obj.plot()
- self.plotcanvas.fit_view()
- if self.is_legacy:
- self.plotcanvas.graph_event_disconnect(self.mp_zc)
- else:
- self.plotcanvas.graph_event_disconnect('mouse_press', self.on_set_zero_click)
- self.worker_task.emit({'fcn': worker_task, 'params': []})
- self.inform.emit(_('Click to set the origin ...'))
- self.mp_zc = self.plotcanvas.graph_event_connect('mouse_press', self.on_set_zero_click)
- # first disconnect it as it may have been used by something else
- try:
- self.replot_signal.disconnect()
- except TypeError:
- pass
- self.replot_signal[list].connect(origin_replot)
- def on_set_zero_click(self, event, location=None, noplot=False, use_thread=True):
- """
- :param event:
- :param location:
- :param noplot:
- :param use_thread:
- :return:
- """
- noplot_sig = noplot
- def worker_task():
- with self.proc_container.new(_("Setting Origin...")):
- for obj in self.collection.get_list():
- obj.offset((x, y))
- self.object_changed.emit(obj)
- # Update the object bounding box options
- a, b, c, d = obj.bounds()
- obj.options['xmin'] = a
- obj.options['ymin'] = b
- obj.options['xmax'] = c
- obj.options['ymax'] = d
- self.inform.emit('[success] %s...' %
- _('Origin set'))
- if noplot_sig is False:
- self.replot_signal.emit([])
- if location is not None:
- if len(location) != 2:
- self.inform.emit('[ERROR_NOTCL] %s...' %
- _("Origin coordinates specified but incomplete."))
- return 'fail'
- x, y = location
- if use_thread is True:
- self.worker_task.emit({'fcn': worker_task, 'params': []})
- else:
- worker_task()
- self.should_we_save = True
- return
- if event.button == 1:
- if self.is_legacy is False:
- event_pos = event.pos
- else:
- event_pos = (event.xdata, event.ydata)
- pos_canvas = self.plotcanvas.translate_coords(event_pos)
- if self.grid_status() == True:
- pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
- else:
- pos = pos_canvas
- x = 0 - pos[0]
- y = 0 - pos[1]
- if use_thread is True:
- self.worker_task.emit({'fcn': worker_task, 'params': []})
- else:
- worker_task()
- self.should_we_save = True
- def on_jump_to(self, custom_location=None, fit_center=True):
- """
- Jump to a location by setting the mouse cursor location
- :return:
- """
- self.report_usage("on_jump_to()")
- # if self.is_legacy is True:
- # self.inform.emit(_("Not available with the current Graphic Engine Legacy(2D)."))
- # return
- if not custom_location:
- dia_box_location = None
- try:
- dia_box_location = eval(self.clipboard.text())
- except Exception as e:
- pass
- if type(dia_box_location) == tuple:
- dia_box_location = str(dia_box_location)
- else:
- dia_box_location = None
- dia_box = Dialog_box(title=_("Jump to ..."),
- label=_("Enter the coordinates in format X,Y:"),
- icon=QtGui.QIcon('share/jump_to16.png'),
- initial_text=dia_box_location)
- if dia_box.ok is True:
- try:
- location = eval(dia_box.location)
- if not isinstance(location, tuple):
- self.inform.emit(_("Wrong coordinates. Enter coordinates in format: X,Y"))
- return
- except:
- return
- else:
- return
- else:
- location = custom_location
- if fit_center:
- self.plotcanvas.fit_center(loc=location)
- cursor = QtGui.QCursor()
- if self.is_legacy is False:
- canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0))
- jump_loc = self.plotcanvas.translate_coords_2((location[0], location[1]))
- j_pos = (canvas_origin.x() + jump_loc[0], (canvas_origin.y() + jump_loc[1]))
- cursor.setPos(j_pos[0], j_pos[1])
- else:
- # find the canvas origin which is in the top left corner
- canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0))
- # determine the coordinates for the lowest left point of the canvas
- x0, y0 = canvas_origin.x(), canvas_origin.y() + self.ui.right_layout.geometry().height()
- # transform the given location from data coordinates to display coordinates. THe display coordinates are
- # in pixels where the origin 0,0 is in the lowest left point of the display window (in our case is the
- # canvas) and the point (width, height) is in the top-right location
- loc = self.plotcanvas.axes.transData.transform_point(location)
- j_pos = (x0 + loc[0], y0 - loc[1])
- cursor.setPos(j_pos[0], j_pos[1])
- if self.grid_status() == True:
- # Update cursor
- self.app_cursor.set_data(np.asarray([(location[0], location[1])]),
- symbol='++', edge_color='black', size=self.defaults["global_cursor_size"])
- # Set the position label
- self.ui.position_label.setText(" <b>X</b>: %.4f "
- "<b>Y</b>: %.4f" % (location[0], location[1]))
- # Set the relative position label
- dx = location[0] - float(self.rel_point1[0])
- dy = location[1] - float(self.rel_point1[1])
- self.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
- "%.4f " % (dx, dy))
- self.inform.emit('[success] %s' %
- _("Done."))
- return location
- def on_copy_object(self):
- self.report_usage("on_copy_object()")
- def initialize(obj_init, app):
- obj_init.solid_geometry = deepcopy(obj.solid_geometry)
- try:
- obj_init.follow_geometry = deepcopy(obj.follow_geometry)
- except AttributeError:
- pass
- try:
- obj_init.apertures = deepcopy(obj.apertures)
- except AttributeError:
- pass
- try:
- if obj.tools:
- obj_init.tools = deepcopy(obj.tools)
- except Exception as e:
- log.debug("App.on_copy_object() --> %s" % str(e))
- def initialize_excellon(obj_init, app):
- obj_init.tools = deepcopy(obj.tools)
- # drills are offset, so they need to be deep copied
- obj_init.drills = deepcopy(obj.drills)
- # slots are offset, so they need to be deep copied
- obj_init.slots = deepcopy(obj.slots)
- obj_init.create_geometry()
- def initialize_script(obj_init, app_obj):
- obj_init.source_file = deepcopy(obj.source_file)
- def initialize_document(obj_init, app_obj):
- obj_init.source_file = deepcopy(obj.source_file)
- for obj in self.collection.get_selected():
- obj_name = obj.options["name"]
- try:
- if isinstance(obj, FlatCAMExcellon):
- self.new_object("excellon", str(obj_name) + "_copy", initialize_excellon)
- elif isinstance(obj, FlatCAMGerber):
- self.new_object("gerber", str(obj_name) + "_copy", initialize)
- elif isinstance(obj, FlatCAMGeometry):
- self.new_object("geometry", str(obj_name) + "_copy", initialize)
- elif isinstance(obj, FlatCAMScript):
- self.new_object("script", str(obj_name) + "_copy", initialize_script)
- elif isinstance(obj, FlatCAMDocument):
- self.new_object("document", str(obj_name) + "_copy", initialize_document)
- except Exception as e:
- return "Operation failed: %s" % str(e)
- def on_copy_object2(self, custom_name):
- def initialize_geometry(obj_init, app):
- obj_init.solid_geometry = deepcopy(obj.solid_geometry)
- try:
- obj_init.follow_geometry = deepcopy(obj.follow_geometry)
- except AttributeError:
- pass
- try:
- obj_init.apertures = deepcopy(obj.apertures)
- except AttributeError:
- pass
- try:
- if obj.tools:
- obj_init.tools = deepcopy(obj.tools)
- except Exception as e:
- log.debug("on_copy_object2() --> %s" % str(e))
- def initialize_gerber(obj_init, app):
- obj_init.solid_geometry = deepcopy(obj.solid_geometry)
- obj_init.apertures = deepcopy(obj.apertures)
- obj_init.aperture_macros = deepcopy(obj.aperture_macros)
- def initialize_excellon(obj_init, app):
- obj_init.tools = deepcopy(obj.tools)
- # drills are offset, so they need to be deep copied
- obj_init.drills = deepcopy(obj.drills)
- # slots are offset, so they need to be deep copied
- obj_init.slots = deepcopy(obj.slots)
- obj_init.create_geometry()
- for obj in self.collection.get_selected():
- obj_name = obj.options["name"]
- try:
- if isinstance(obj, FlatCAMExcellon):
- self.new_object("excellon", str(obj_name) + custom_name, initialize_excellon)
- elif isinstance(obj, FlatCAMGerber):
- self.new_object("gerber", str(obj_name) + custom_name, initialize_gerber)
- elif isinstance(obj, FlatCAMGeometry):
- self.new_object("geometry", str(obj_name) + custom_name, initialize_geometry)
- except Exception as e:
- return "Operation failed: %s" % str(e)
- def on_rename_object(self, text):
- self.report_usage("on_rename_object()")
- named_obj = self.collection.get_active()
- for obj in named_obj:
- if obj is list:
- self.on_rename_object(text)
- else:
- try:
- obj.options['name'] = text
- except Exception as e:
- log.warning("App.on_rename_object() --> Could not rename the object in the list. --> %s" % str(e))
- def convert_any2geo(self):
- self.report_usage("convert_any2geo()")
- def initialize(obj_init, app):
- obj_init.solid_geometry = obj.solid_geometry
- try:
- obj_init.follow_geometry = obj.follow_geometry
- except AttributeError:
- pass
- try:
- obj_init.apertures = obj.apertures
- except AttributeError:
- pass
- try:
- if obj.tools:
- obj_init.tools = obj.tools
- except AttributeError:
- pass
- def initialize_excellon(obj_init, app):
- # objs = self.collection.get_selected()
- # FlatCAMGeometry.merge(objs, obj)
- solid_geo = []
- for tool in obj.tools:
- for geo in obj.tools[tool]['solid_geometry']:
- solid_geo.append(geo)
- obj_init.solid_geometry = deepcopy(solid_geo)
- if not self.collection.get_selected():
- log.warning("App.convert_any2geo --> No object selected")
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object is selected. Select an object and try again."))
- return
- for obj in self.collection.get_selected():
- obj_name = obj.options["name"]
- try:
- if isinstance(obj, FlatCAMExcellon):
- self.new_object("geometry", str(obj_name) + "_conv", initialize_excellon)
- else:
- self.new_object("geometry", str(obj_name) + "_conv", initialize)
- except Exception as e:
- return "Operation failed: %s" % str(e)
- def convert_any2gerber(self):
- self.report_usage("convert_any2gerber()")
- def initialize_geometry(obj_init, app):
- apertures = {}
- apid = 0
- apertures[str(apid)] = {}
- apertures[str(apid)]['geometry'] = []
- for obj_orig in obj.solid_geometry:
- new_elem = dict()
- new_elem['solid'] = obj_orig
- new_elem['follow'] = obj_orig.exterior
- apertures[str(apid)]['geometry'].append(deepcopy(new_elem))
- apertures[str(apid)]['size'] = 0.0
- apertures[str(apid)]['type'] = 'C'
- obj_init.solid_geometry = deepcopy(obj.solid_geometry)
- obj_init.apertures = deepcopy(apertures)
- def initialize_excellon(obj_init, app):
- apertures = {}
- apid = 10
- for tool in obj.tools:
- apertures[str(apid)] = {}
- apertures[str(apid)]['geometry'] = []
- for geo in obj.tools[tool]['solid_geometry']:
- new_el = dict()
- new_el['solid'] = geo
- new_el['follow'] = geo.exterior
- apertures[str(apid)]['geometry'].append(deepcopy(new_el))
- apertures[str(apid)]['size'] = float(obj.tools[tool]['C'])
- apertures[str(apid)]['type'] = 'C'
- apid += 1
- # create solid_geometry
- solid_geometry = []
- for apid in apertures:
- for geo_el in apertures[apid]['geometry']:
- solid_geometry.append(geo_el['solid'])
- solid_geometry = MultiPolygon(solid_geometry)
- solid_geometry = solid_geometry.buffer(0.0000001)
- obj_init.solid_geometry = deepcopy(solid_geometry)
- obj_init.apertures = deepcopy(apertures)
- # clear the working objects (perhaps not necessary due of Python GC)
- apertures.clear()
- if not self.collection.get_selected():
- log.warning("App.convert_any2gerber --> No object selected")
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object is selected. Select an object and try again."))
- return
- for obj in self.collection.get_selected():
- obj_name = obj.options["name"]
- try:
- if isinstance(obj, FlatCAMExcellon):
- self.new_object("gerber", str(obj_name) + "_conv", initialize_excellon)
- elif isinstance(obj, FlatCAMGeometry):
- self.new_object("gerber", str(obj_name) + "_conv", initialize_geometry)
- else:
- log.warning("App.convert_any2gerber --> This is no vaild object for conversion.")
- except Exception as e:
- return "Operation failed: %s" % str(e)
- def abort_all_tasks(self):
- if self.abort_flag is False:
- self.inform.emit(_("Aborting. The current task will be gracefully closed as soon as possible..."))
- self.abort_flag = True
- def app_is_idle(self):
- if self.abort_flag:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("The current task was gracefully closed on user request..."))
- self.abort_flag = False
- def on_selectall(self):
- self.report_usage("on_selectall()")
- # delete the possible selection box around a possible selected object
- self.delete_selection_shape()
- for name in self.collection.get_names():
- self.collection.set_active(name)
- curr_sel_obj = self.collection.get_by_name(name)
- # create the selection box around the selected object
- if self.defaults['global_selection_shape'] is True:
- self.draw_selection_shape(curr_sel_obj)
- def on_preferences(self):
- # add the tab if it was closed
- self.ui.plot_tab_area.addTab(self.ui.preferences_tab, _("Preferences"))
- # delete the absolute and relative position and messages in the infobar
- self.ui.position_label.setText("")
- self.ui.rel_position_label.setText("")
- # Switch plot_area to preferences page
- self.ui.plot_tab_area.setCurrentWidget(self.ui.preferences_tab)
- # self.ui.show()
- # this disconnect() is done so the slot will be connected only once
- try:
- self.ui.plot_tab_area.tab_closed_signal.disconnect(self.on_preferences_closed)
- except (TypeError, AttributeError):
- pass
- self.ui.plot_tab_area.tab_closed_signal.connect(self.on_preferences_closed)
- # detect changes in the preferences
- for idx in range(self.ui.pref_tab_area.count()):
- for tb in self.ui.pref_tab_area.widget(idx).findChildren(QtCore.QObject):
- try:
- try:
- tb.textEdited.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- tb.textEdited.connect(self.on_preferences_edited)
- except AttributeError:
- pass
- try:
- try:
- tb.modificationChanged.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- tb.modificationChanged.connect(self.on_preferences_edited)
- except AttributeError:
- pass
- try:
- try:
- tb.toggled.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- tb.toggled.connect(self.on_preferences_edited)
- except AttributeError:
- pass
- try:
- try:
- tb.valueChanged.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- tb.valueChanged.connect(self.on_preferences_edited)
- except AttributeError:
- pass
- try:
- try:
- tb.currentIndexChanged.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- tb.currentIndexChanged.connect(self.on_preferences_edited)
- except AttributeError:
- pass
- def on_preferences_edited(self):
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Preferences edited but not saved."))
- self.preferences_changed_flag = True
- def on_preferences_closed(self):
- # disconnect
- for idx in range(self.ui.pref_tab_area.count()):
- for tb in self.ui.pref_tab_area.widget(idx).findChildren(QtCore.QObject):
- try:
- tb.textEdited.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- try:
- tb.modificationChanged.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- try:
- tb.toggled.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- try:
- tb.valueChanged.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- try:
- tb.currentIndexChanged.disconnect(self.on_preferences_edited)
- except (TypeError, AttributeError):
- pass
- if self.preferences_changed_flag is True:
- msgbox = QtWidgets.QMessageBox()
- msgbox.setText(_("One or more values are changed.\n"
- "Do you want to save the Preferences?"))
- msgbox.setWindowTitle(_("Save Preferences"))
- msgbox.setWindowIcon(QtGui.QIcon('share/save_as.png'))
- bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
- bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
- msgbox.setDefaultButton(bt_yes)
- msgbox.exec_()
- response = msgbox.clickedButton()
- if response == bt_yes:
- self.on_save_button()
- self.inform.emit('[success] %s' %
- _("Preferences saved."))
- else:
- self.preferences_changed_flag = False
- return
- def on_flipy(self):
- self.report_usage("on_flipy()")
- obj_list = self.collection.get_selected()
- xminlist = []
- yminlist = []
- xmaxlist = []
- ymaxlist = []
- if not obj_list:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected to Flip on Y axis."))
- else:
- try:
- # first get a bounding box to fit all
- for obj in obj_list:
- xmin, ymin, xmax, ymax = obj.bounds()
- xminlist.append(xmin)
- yminlist.append(ymin)
- xmaxlist.append(xmax)
- ymaxlist.append(ymax)
- # get the minimum x,y and maximum x,y for all objects selected
- xminimal = min(xminlist)
- yminimal = min(yminlist)
- xmaximal = max(xmaxlist)
- ymaximal = max(ymaxlist)
- px = 0.5 * (xminimal + xmaximal)
- py = 0.5 * (yminimal + ymaximal)
- # execute mirroring
- for obj in obj_list:
- obj.mirror('X', [px, py])
- obj.plot()
- self.object_changed.emit(obj)
- self.inform.emit('[success] %s' %
- _("Flip on Y axis done."))
- except Exception as e:
- self.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Flip action was not executed."), str(e)))
- return
- def on_flipx(self):
- self.report_usage("on_flipx()")
- obj_list = self.collection.get_selected()
- xminlist = []
- yminlist = []
- xmaxlist = []
- ymaxlist = []
- if not obj_list:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected to Flip on X axis."))
- else:
- try:
- # first get a bounding box to fit all
- for obj in obj_list:
- xmin, ymin, xmax, ymax = obj.bounds()
- xminlist.append(xmin)
- yminlist.append(ymin)
- xmaxlist.append(xmax)
- ymaxlist.append(ymax)
- # get the minimum x,y and maximum x,y for all objects selected
- xminimal = min(xminlist)
- yminimal = min(yminlist)
- xmaximal = max(xmaxlist)
- ymaximal = max(ymaxlist)
- px = 0.5 * (xminimal + xmaximal)
- py = 0.5 * (yminimal + ymaximal)
- # execute mirroring
- for obj in obj_list:
- obj.mirror('Y', [px, py])
- obj.plot()
- self.object_changed.emit(obj)
- self.inform.emit('[success] %s' %
- _("Flip on X axis done."))
- except Exception as e:
- self.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Flip action was not executed."), str(e)))
- return
- def on_rotate(self, silent=False, preset=None):
- self.report_usage("on_rotate()")
- obj_list = self.collection.get_selected()
- xminlist = []
- yminlist = []
- xmaxlist = []
- ymaxlist = []
- if not obj_list:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected to Rotate."))
- else:
- if silent is False:
- rotatebox = FCInputDialog(title=_("Transform"), text=_("Enter the Angle value:"),
- min=-360, max=360, decimals=4,
- init_val=float(self.defaults['tools_transform_rotate']))
- num, ok = rotatebox.get_value()
- else:
- num = preset
- ok = True
- if ok:
- try:
- # first get a bounding box to fit all
- for obj in obj_list:
- xmin, ymin, xmax, ymax = obj.bounds()
- xminlist.append(xmin)
- yminlist.append(ymin)
- xmaxlist.append(xmax)
- ymaxlist.append(ymax)
- # get the minimum x,y and maximum x,y for all objects selected
- xminimal = min(xminlist)
- yminimal = min(yminlist)
- xmaximal = max(xmaxlist)
- ymaximal = max(ymaxlist)
- px = 0.5 * (xminimal + xmaximal)
- py = 0.5 * (yminimal + ymaximal)
- for sel_obj in obj_list:
- sel_obj.rotate(-float(num), point=(px, py))
- sel_obj.plot()
- self.object_changed.emit(sel_obj)
- self.inform.emit('[success] %s' %
- _("Rotation done."))
- except Exception as e:
- self.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Rotation movement was not executed."), str(e)))
- return
- def on_skewx(self):
- self.report_usage("on_skewx()")
- obj_list = self.collection.get_selected()
- xminlist = []
- yminlist = []
- if not obj_list:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected to Skew/Shear on X axis."))
- else:
- skewxbox = FCInputDialog(title=_("Transform"), text=_("Enter the Angle value:"),
- min=-360, max=360, decimals=4,
- init_val=float(self.defaults['tools_transform_skew_x']))
- num, ok = skewxbox.get_value()
- if ok:
- # first get a bounding box to fit all
- for obj in obj_list:
- xmin, ymin, xmax, ymax = obj.bounds()
- xminlist.append(xmin)
- yminlist.append(ymin)
- # get the minimum x,y and maximum x,y for all objects selected
- xminimal = min(xminlist)
- yminimal = min(yminlist)
- for obj in obj_list:
- obj.skew(num, 0, point=(xminimal, yminimal))
- obj.plot()
- self.object_changed.emit(obj)
- self.inform.emit('[success] %s' %
- _("Skew on X axis done."))
- def on_skewy(self):
- self.report_usage("on_skewy()")
- obj_list = self.collection.get_selected()
- xminlist = []
- yminlist = []
- if not obj_list:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected to Skew/Shear on Y axis."))
- else:
- skewybox = FCInputDialog(title=_("Transform"), text=_("Enter the Angle value:"),
- min=-360, max=360, decimals=4,
- init_val=float(self.defaults['tools_transform_skew_y']))
- num, ok = skewybox.get_value()
- if ok:
- # first get a bounding box to fit all
- for obj in obj_list:
- xmin, ymin, xmax, ymax = obj.bounds()
- xminlist.append(xmin)
- yminlist.append(ymin)
- # get the minimum x,y and maximum x,y for all objects selected
- xminimal = min(xminlist)
- yminimal = min(yminlist)
- for obj in obj_list:
- obj.skew(0, num, point=(xminimal, yminimal))
- obj.plot()
- self.object_changed.emit(obj)
- self.inform.emit('[success] %s' %
- _("Skew on Y axis done."))
- def on_plots_updated(self):
- """
- Callback used to report when the plots have changed.
- Adjust axes and zooms to fit.
- :return: None
- """
- if self.is_legacy is False:
- self.plotcanvas.update() # TODO: Need update canvas?
- else:
- self.plotcanvas.auto_adjust_axes()
- self.on_zoom_fit(None)
- self.collection.update_view()
- # self.inform.emit(_("Plots updated ..."))
- # TODO: Rework toolbar 'clear', 'replot' functions
- def on_toolbar_replot(self):
- """
- Callback for toolbar button. Re-plots all objects.
- :return: None
- """
- self.report_usage("on_toolbar_replot")
- self.log.debug("on_toolbar_replot()")
- try:
- self.collection.get_active().read_form()
- except AttributeError:
- self.log.debug("on_toolbar_replot(): AttributeError")
- pass
- self.plot_all()
- def on_row_activated(self, index):
- if index.isValid():
- if index.internalPointer().parent_item != self.collection.root_item:
- self.ui.notebook.setCurrentWidget(self.ui.selected_tab)
- self.collection.on_item_activated(index)
- def on_collection_updated(self, obj, state, old_name):
- """
- Create a menu from the object loaded in the collection.
- TODO: should use the collection model to do this
- :param obj: object that was changd (added, deleted, renamed)
- :param state: what was done with the objectCand be: added, deleted, delete_all, renamed
- :param old_name: the old name of the object before the action that triggered this slot happened
- :return: None
- """
- icon_files = {
- "gerber": "share/flatcam_icon16.png",
- "excellon": "share/drill16.png",
- "cncjob": "share/cnc16.png",
- "geometry": "share/geometry16.png",
- "script": "share/script_new16.png",
- "document": "share/notes16_1.png"
- }
- if state == 'append':
- for act in self.ui.menuobjects.actions():
- try:
- act.triggered.disconnect()
- except TypeError:
- pass
- self.ui.menuobjects.clear()
- gerber_list = list()
- exc_list = list()
- cncjob_list = list()
- geo_list = list()
- script_list = list()
- doc_list = list()
- for name in self.collection.get_names():
- obj_named = self.collection.get_by_name(name)
- if obj_named.kind == 'gerber':
- gerber_list.append(name)
- elif obj_named.kind == 'excellon':
- exc_list.append(name)
- elif obj_named.kind == 'cncjob':
- cncjob_list.append(name)
- elif obj_named.kind == 'geometry':
- geo_list.append(name)
- elif obj_named.kind == 'script':
- script_list.append(name)
- elif obj_named.kind == 'document':
- doc_list.append(name)
- def add_act(name):
- obj_for_icon = self.collection.get_by_name(name)
- add_action = QtWidgets.QAction(parent=self.ui.menuobjects)
- add_action.setText(name)
- add_action.setIcon(QtGui.QIcon(icon_files[obj_for_icon.kind]))
- add_action.triggered.connect(lambda: self.collection.set_exclusive_active(name))
- self.ui.menuobjects.addAction(add_action)
- for name in gerber_list:
- add_act(name)
- self.ui.menuobjects.addSeparator()
- for name in exc_list:
- add_act(name)
- self.ui.menuobjects.addSeparator()
- for name in cncjob_list:
- add_act(name)
- self.ui.menuobjects.addSeparator()
- for name in geo_list:
- add_act(name)
- self.ui.menuobjects.addSeparator()
- for name in script_list:
- add_act(name)
- self.ui.menuobjects.addSeparator()
- for name in doc_list:
- add_act(name)
- elif state == 'delete':
- for act in self.ui.menuobjects.actions():
- if act.text() == obj.options['name']:
- try:
- act.triggered.disconnect()
- except TypeError:
- pass
- self.ui.menuobjects.removeAction(act)
- break
- elif state == 'rename':
- for act in self.ui.menuobjects.actions():
- if act.text() == old_name:
- add_action = QtWidgets.QAction(parent=self.ui.menuobjects)
- add_action.setText(obj.options['name'])
- add_action.setIcon(QtGui.QIcon(icon_files[obj.kind]))
- add_action.triggered.connect(lambda: self.collection.set_exclusive_active(obj.options['name']))
- self.ui.menuobjects.insertAction(act, add_action)
- try:
- act.triggered.disconnect()
- except TypeError:
- pass
- self.ui.menuobjects.removeAction(act)
- break
- elif state =='delete_all':
- for act in self.ui.menuobjects.actions():
- try:
- act.triggered.disconnect()
- except TypeError:
- pass
- self.ui.menuobjects.clear()
- def grid_status(self):
- if self.ui.grid_snap_btn.isChecked():
- return True
- else:
- return False
- def populate_cmenu_grids(self):
- units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
- self.ui.cmenu_gridmenu.clear()
- sorted_list = sorted(self.defaults["global_grid_context_menu"][str(units)])
- grid_toggle = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon('share/grid32_menu.png'), _("Grid On/Off"))
- grid_toggle.setCheckable(True)
- if self.grid_status() == True:
- grid_toggle.setChecked(True)
- else:
- grid_toggle.setChecked(False)
- self.ui.cmenu_gridmenu.addSeparator()
- for grid in sorted_list:
- action = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon('share/grid32_menu.png'), "%s" % str(grid))
- action.triggered.connect(self.set_grid)
- self.ui.cmenu_gridmenu.addSeparator()
- grid_add = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon('share/plus32.png'), _("Add"))
- grid_delete = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon('share/delete32.png'), _("Delete"))
- grid_add.triggered.connect(self.on_grid_add)
- grid_delete.triggered.connect(self.on_grid_delete)
- grid_toggle.triggered.connect(lambda: self.ui.grid_snap_btn.trigger())
- def set_grid(self):
- self.ui.grid_gap_x_entry.setText(self.sender().text())
- self.ui.grid_gap_y_entry.setText(self.sender().text())
- def on_grid_add(self):
- # ## Current application units in lower Case
- units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
- grid_add_popup = FCInputDialog(title=_("New Grid ..."),
- text=_('Enter a Grid Value:'),
- min=0.0000, max=99.9999, decimals=4)
- grid_add_popup.setWindowIcon(QtGui.QIcon('share/plus32.png'))
- val, ok = grid_add_popup.get_value()
- if ok:
- if float(val) == 0:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Please enter a grid value with non-zero value, in Float format."))
- return
- else:
- if val not in self.defaults["global_grid_context_menu"][str(units)]:
- self.defaults["global_grid_context_menu"][str(units)].append(val)
- self.inform.emit('[success] %s...' %
- _("New Grid added"))
- else:
- self.inform.emit('[WARNING_NOTCL] %s...' %
- _("Grid already exists"))
- else:
- self.inform.emit('[WARNING_NOTCL] %s...' %
- _("Adding New Grid cancelled"))
- def on_grid_delete(self):
- # ## Current application units in lower Case
- units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
- grid_del_popup = FCInputDialog(title="Delete Grid ...",
- text='Enter a Grid Value:',
- min=0.0000, max=99.9999, decimals=4)
- grid_del_popup.setWindowIcon(QtGui.QIcon('share/delete32.png'))
- val, ok = grid_del_popup.get_value()
- if ok:
- if float(val) == 0:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Please enter a grid value with non-zero value, in Float format."))
- return
- else:
- try:
- self.defaults["global_grid_context_menu"][str(units)].remove(val)
- except ValueError:
- self.inform.emit('[ERROR_NOTCL]%s...' %
- _(" Grid Value does not exist"))
- return
- self.inform.emit('[success] %s...' %
- _("Grid Value deleted"))
- else:
- self.inform.emit('[WARNING_NOTCL] %s...' %
- _("Delete Grid value cancelled"))
- def on_shortcut_list(self):
- self.report_usage("on_shortcut_list()")
- # add the tab if it was closed
- self.ui.plot_tab_area.addTab(self.ui.shortcuts_tab, _("Key Shortcut List"))
- # delete the absolute and relative position and messages in the infobar
- self.ui.position_label.setText("")
- self.ui.rel_position_label.setText("")
- # Switch plot_area to preferences page
- self.ui.plot_tab_area.setCurrentWidget(self.ui.shortcuts_tab)
- # self.ui.show()
- def on_select_tab(self, name):
- # if the splitter is hidden, display it, else hide it but only if the current widget is the same
- if self.ui.splitter.sizes()[0] == 0:
- self.ui.splitter.setSizes([1, 1])
- else:
- if self.ui.notebook.currentWidget().objectName() == name + '_tab':
- self.ui.splitter.setSizes([0, 1])
- if name == 'project':
- self.ui.notebook.setCurrentWidget(self.ui.project_tab)
- elif name == 'selected':
- self.ui.notebook.setCurrentWidget(self.ui.selected_tab)
- elif name == 'tool':
- self.ui.notebook.setCurrentWidget(self.ui.tool_tab)
- def on_copy_name(self):
- self.report_usage("on_copy_name()")
- obj = self.collection.get_active()
- try:
- name = obj.options["name"]
- except AttributeError:
- log.debug("on_copy_name() --> No object selected to copy it's name")
- self.inform.emit('[WARNING_NOTCL]%s' %
- _(" No object selected to copy it's name"))
- return
- self.clipboard.setText(name)
- self.inform.emit(_("Name copied on clipboard ..."))
- def on_mouse_click_over_plot(self, event):
- """
- Default actions are:
- :param event: Contains information about the event, like which button
- was clicked, the pixel coordinates and the axes coordinates.
- :return: None
- """
- self.pos = []
- if self.is_legacy is False:
- event_pos = event.pos
- if self.defaults["global_pan_button"] == '2':
- pan_button = 2
- else:
- pan_button = 3
- # Set the mouse button for panning
- self.plotcanvas.view.camera.pan_button_setting = pan_button
- else:
- event_pos = (event.xdata, event.ydata)
- # Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
- if self.defaults["global_pan_button"] == '2':
- pan_button = 3
- else:
- pan_button = 2
- # So it can receive key presses
- self.plotcanvas.native.setFocus()
- self.pos_canvas = self.plotcanvas.translate_coords(event_pos)
- if self.grid_status() == True:
- self.pos = self.geo_editor.snap(self.pos_canvas[0], self.pos_canvas[1])
- else:
- self.pos = (self.pos_canvas[0], self.pos_canvas[1])
- try:
- if event.button == 1:
- # Reset here the relative coordinates so there is a new reference on the click position
- if self.rel_point1 is None:
- self.rel_point1 = self.pos
- else:
- self.rel_point2 = copy(self.rel_point1)
- self.rel_point1 = self.pos
- self.on_mouse_move_over_plot(event, origin_click=True)
- except Exception as e:
- App.log.debug("App.on_mouse_click_over_plot() --> Outside plot? --> %s" % str(e))
- def on_double_click_over_plot(self, event):
- if event.button == 1:
- self.doubleclick = True
- def on_mouse_move_over_plot(self, event, origin_click=None):
- """
- Callback for the mouse motion event over the plot.
- :param event: Contains information about the event.
- :param origin_click
- :return: None
- """
- if self.is_legacy is False:
- event_pos = event.pos
- if self.defaults["global_pan_button"] == '2':
- pan_button = 2
- else:
- pan_button = 3
- event_is_dragging = event.is_dragging
- else:
- event_pos = (event.xdata, event.ydata)
- # Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
- if self.defaults["global_pan_button"] == '2':
- pan_button = 3
- else:
- pan_button = 2
- event_is_dragging = self.plotcanvas.is_dragging
- # So it can receive key presses
- self.plotcanvas.native.setFocus()
- self.pos_jump = event_pos
- self.ui.popMenu.mouse_is_panning = False
- if not origin_click:
- # if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
- if event.button == pan_button and event_is_dragging == 1:
- self.ui.popMenu.mouse_is_panning = True
- return
- if self.rel_point1 is not None:
- try: # May fail in case mouse not within axes
- pos_canvas = self.plotcanvas.translate_coords(event_pos)
- if self.grid_status() == True:
- pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
- # Update cursor
- self.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
- symbol='++', edge_color='black', size=self.defaults["global_cursor_size"])
- else:
- pos = (pos_canvas[0], pos_canvas[1])
- self.ui.position_label.setText(" <b>X</b>: %.4f "
- "<b>Y</b>: %.4f" % (pos[0], pos[1]))
- dx = pos[0] - float(self.rel_point1[0])
- dy = pos[1] - float(self.rel_point1[1])
- self.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
- "%.4f " % (dx, dy))
- self.mouse = [pos[0], pos[1]]
- # if the mouse is moved and the LMB is clicked then the action is a selection
- if event_is_dragging == 1 and event.button == 1:
- self.delete_selection_shape()
- if dx < 0:
- self.draw_moving_selection_shape(self.pos, pos, color=self.defaults['global_alt_sel_line'],
- face_color=self.defaults['global_alt_sel_fill'])
- self.selection_type = False
- elif dx > 0:
- self.draw_moving_selection_shape(self.pos, pos)
- self.selection_type = True
- else:
- self.selection_type = None
- # hover effect - enabled in Preferences -> General -> GUI Settings
- if self.defaults['global_hover']:
- for obj in self.collection.get_list():
- try:
- # select the object(s) only if it is enabled (plotted)
- if obj.options['plot']:
- if obj not in self.collection.get_selected():
- poly_obj = Polygon(
- [(obj.options['xmin'], obj.options['ymin']),
- (obj.options['xmax'], obj.options['ymin']),
- (obj.options['xmax'], obj.options['ymax']),
- (obj.options['xmin'], obj.options['ymax'])]
- )
- if Point(pos).within(poly_obj):
- if obj.isHovering is False:
- obj.isHovering = True
- obj.notHovering = True
- # create the selection box around the selected object
- self.draw_hover_shape(obj, color='#d1e0e0FF')
- else:
- if obj.notHovering is True:
- obj.notHovering = False
- obj.isHovering = False
- self.delete_hover_shape()
- except:
- # the Exception here will happen if we try to select on screen and we have an
- # newly (and empty) just created Geometry or Excellon object that do not have the
- # xmin, xmax, ymin, ymax options.
- # In this case poly_obj creation (see above) will fail
- pass
- except:
- self.ui.position_label.setText("")
- self.ui.rel_position_label.setText("")
- self.mouse = None
- def on_mouse_click_release_over_plot(self, event):
- """
- Callback for the mouse click release over plot. This event is generated by the Matplotlib backend
- and has been registered in ''self.__init__()''.
- :param event: contains information about the event.
- :return:
- """
- pos = 0, 0
- if self.is_legacy is False:
- event_pos = event.pos
- right_button = 2
- else:
- event_pos = (event.xdata, event.ydata)
- # Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
- right_button = 3
- pos_canvas = self.plotcanvas.translate_coords(event_pos)
- if self.grid_status() == True:
- pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
- else:
- pos = (pos_canvas[0], pos_canvas[1])
- # if the released mouse button was RMB then test if it was a panning motion or not, if not it was a context
- # canvas menu
- if event.button == right_button: # right click
- if self.ui.popMenu.mouse_is_panning is False:
- self.cursor = QtGui.QCursor()
- self.populate_cmenu_grids()
- self.ui.popMenu.popup(self.cursor.pos())
- # if the released mouse button was LMB then test if we had a right-to-left selection or a left-to-right
- # selection and then select a type of selection ("enclosing" or "touching")
- try:
- if event.button == 1: # left click
- modifiers = QtWidgets.QApplication.keyboardModifiers()
- # If the SHIFT key is pressed when LMB is clicked then the coordinates are copied to clipboard
- if modifiers == QtCore.Qt.ShiftModifier:
- # do not auto open the Project Tab
- self.click_noproject = True
- self.clipboard.setText(self.defaults["global_point_clipboard_format"] % (self.pos[0], self.pos[1]))
- self.inform.emit('[success] %s' %
- _("Coordinates copied to clipboard."))
- return
- if self.doubleclick is True:
- self.doubleclick = False
- if self.collection.get_selected():
- self.ui.notebook.setCurrentWidget(self.ui.selected_tab)
- if self.ui.splitter.sizes()[0] == 0:
- self.ui.splitter.setSizes([1, 1])
- # delete the selection shape(S) as it may be in the way
- self.delete_selection_shape()
- self.delete_hover_shape()
- else:
- if self.selection_type is not None:
- self.selection_area_handler(self.pos, pos, self.selection_type)
- self.selection_type = None
- else:
- key_modifier = QtWidgets.QApplication.keyboardModifiers()
- if key_modifier == QtCore.Qt.ShiftModifier:
- mod_key = 'Shift'
- elif key_modifier == QtCore.Qt.ControlModifier:
- mod_key = 'Control'
- else:
- mod_key = None
- if mod_key == self.defaults["global_mselect_key"]:
- # If the CTRL key is pressed when the LMB is clicked then if the object is selected it will
- # deselect, and if it's not selected then it will be selected
- # If there is no active command (self.command_active is None) then we check if we clicked
- # on a object by checking the bounding limits against mouse click position
- if self.command_active is None:
- self.select_objects(key='CTRL')
- self.delete_hover_shape()
- else:
- # If there is no active command (self.command_active is None) then we check if we clicked
- # on a object by checking the bounding limits against mouse click position
- if self.command_active is None:
- self.select_objects()
- self.delete_hover_shape()
- except Exception as e:
- log.warning("Error: %s" % str(e))
- return
- def selection_area_handler(self, start_pos, end_pos, sel_type):
- """
- :param start_pos: mouse position when the selection LMB click was done
- :param end_pos: mouse position when the left mouse button is released
- :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
- :return:
- """
- poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
- self.delete_selection_shape()
- for obj in self.collection.get_list():
- try:
- # select the object(s) only if it is enabled (plotted)
- if obj.options['plot']:
- poly_obj = Polygon([(obj.options['xmin'], obj.options['ymin']),
- (obj.options['xmax'], obj.options['ymin']),
- (obj.options['xmax'], obj.options['ymax']),
- (obj.options['xmin'], obj.options['ymax'])])
- if sel_type is True:
- if poly_obj.within(poly_selection):
- # create the selection box around the selected object
- if self.defaults['global_selection_shape'] is True:
- self.draw_selection_shape(obj)
- self.collection.set_active(obj.options['name'])
- else:
- if poly_selection.intersects(poly_obj):
- # create the selection box around the selected object
- if self.defaults['global_selection_shape'] is True:
- self.draw_selection_shape(obj)
- self.collection.set_active(obj.options['name'])
- except Exception as e:
- # the Exception here will happen if we try to select on screen and we have an newly (and empty)
- # just created Geometry or Excellon object that do not have the xmin, xmax, ymin, ymax options.
- # In this case poly_obj creation (see above) will fail
- log.debug("App.selection_area_handler() --> %s" % str(e))
- def select_objects(self, key=None):
- # list where we store the overlapped objects under our mouse left click position
- objects_under_the_click_list = []
- # Populate the list with the overlapped objects on the click position
- curr_x, curr_y = self.pos
- for obj in self.all_objects_list:
- if (curr_x >= obj.options['xmin']) and (curr_x <= obj.options['xmax']) and \
- (curr_y >= obj.options['ymin']) and (curr_y <= obj.options['ymax']):
- if obj.options['name'] not in objects_under_the_click_list:
- if obj.options['plot']:
- # add objects to the objects_under_the_click list only if the object is plotted
- # (active and not disabled)
- objects_under_the_click_list.append(obj.options['name'])
- try:
- # If there is no element in the overlapped objects list then make everyone inactive
- # because we selected "nothing"
- if not objects_under_the_click_list:
- self.collection.set_all_inactive()
- # delete the possible selection box around a possible selected object
- self.delete_selection_shape()
- # and as a convenience move the focus to the Project tab because Selected tab is now empty but
- # only when working on App
- if self.call_source == 'app':
- if self.click_noproject is False:
- self.ui.notebook.setCurrentWidget(self.ui.project_tab)
- else:
- # restore auto open the Project Tab
- self.click_noproject = False
- # delete any text in the status bar, implicitly the last object name that was selected
- self.inform.emit("")
- else:
- self.call_source = 'app'
- else:
- # case when there is only an object under the click and we toggle it
- if len(objects_under_the_click_list) == 1:
- if self.collection.get_active() is None:
- self.collection.set_active(objects_under_the_click_list[0])
- # create the selection box around the selected object
- curr_sel_obj = self.collection.get_active()
- if self.defaults['global_selection_shape'] is True:
- self.draw_selection_shape(curr_sel_obj)
- # self.inform.emit('[selected] %s: %s selected' %
- # (str(curr_sel_obj.kind).capitalize(), str(curr_sel_obj.options['name'])))
- if curr_sel_obj.kind == 'gerber':
- self.inform.emit(
- _('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='green', name=str(curr_sel_obj.options['name'])))
- elif curr_sel_obj.kind == 'excellon':
- self.inform.emit(
- _('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='brown', name=str(curr_sel_obj.options['name'])))
- elif curr_sel_obj.kind == 'cncjob':
- self.inform.emit(
- _('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='blue', name=str(curr_sel_obj.options['name'])))
- elif curr_sel_obj.kind == 'geometry':
- self.inform.emit(
- _('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='red', name=str(curr_sel_obj.options['name'])))
- elif self.collection.get_active().options['name'] not in objects_under_the_click_list:
- self.collection.set_all_inactive()
- self.delete_selection_shape()
- self.collection.set_active(objects_under_the_click_list[0])
- # create the selection box around the selected object
- curr_sel_obj = self.collection.get_active()
- if self.defaults['global_selection_shape'] is True:
- self.draw_selection_shape(curr_sel_obj)
- # self.inform.emit('[selected] %s: %s selected' %
- # (str(curr_sel_obj.kind).capitalize(), str(curr_sel_obj.options['name'])))
- if curr_sel_obj.kind == 'gerber':
- self.inform.emit(
- _('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='green', name=str(curr_sel_obj.options['name'])))
- elif curr_sel_obj.kind == 'excellon':
- self.inform.emit(
- _('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='brown', name=str(curr_sel_obj.options['name'])))
- elif curr_sel_obj.kind == 'cncjob':
- self.inform.emit(
- _('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='blue', name=str(curr_sel_obj.options['name'])))
- elif curr_sel_obj.kind == 'geometry':
- self.inform.emit(
- _('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='red', name=str(curr_sel_obj.options['name'])))
- else:
- self.collection.set_all_inactive()
- self.delete_selection_shape()
- if self.call_source == 'app':
- # delete any text in the status bar, implicitly the last object name that was selected
- self.inform.emit("")
- else:
- self.call_source = 'app'
- else:
- # If there is no selected object
- # make active the first element of the overlapped objects list
- if self.collection.get_active() is None:
- self.collection.set_active(objects_under_the_click_list[0])
- name_sel_obj = self.collection.get_active().options['name']
- # In case that there is a selected object but it is not in the overlapped object list
- # make that object inactive and activate the first element in the overlapped object list
- if name_sel_obj not in objects_under_the_click_list:
- self.collection.set_inactive(name_sel_obj)
- name_sel_obj = objects_under_the_click_list[0]
- self.collection.set_active(name_sel_obj)
- else:
- name_sel_obj_idx = objects_under_the_click_list.index(name_sel_obj)
- self.collection.set_all_inactive()
- self.collection.set_active(objects_under_the_click_list[(name_sel_obj_idx + 1) %
- len(objects_under_the_click_list)])
- curr_sel_obj = self.collection.get_active()
- # delete the possible selection box around a possible selected object
- self.delete_selection_shape()
- # create the selection box around the selected object
- if self.defaults['global_selection_shape'] is True:
- self.draw_selection_shape(curr_sel_obj)
- # self.inform.emit('[selected] %s: %s selected' %
- # (str(curr_sel_obj.kind).capitalize(), str(curr_sel_obj.options['name'])))
- if curr_sel_obj.kind == 'gerber':
- self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='green', name=str(curr_sel_obj.options['name'])))
- elif curr_sel_obj.kind == 'excellon':
- self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='brown', name=str(curr_sel_obj.options['name'])))
- elif curr_sel_obj.kind == 'cncjob':
- self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='blue', name=str(curr_sel_obj.options['name'])))
- elif curr_sel_obj.kind == 'geometry':
- self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
- color='red', name=str(curr_sel_obj.options['name'])))
- # for obj in self.collection.get_list():
- # obj.plot()
- # curr_sel_obj.plot(color=self.FC_dark_blue, face_color=self.FC_light_blue)
- # TODO: on selected objects change the object colors and do not draw the selection box
- # self.plotcanvas.update() # this updates the canvas
- except Exception as e:
- log.error("[ERROR] Something went bad. %s" % str(e))
- return
- def delete_hover_shape(self):
- self.hover_shapes.clear()
- self.hover_shapes.redraw()
- def draw_hover_shape(self, sel_obj, color=None):
- """
- :param sel_obj: the object for which the hover shape must be drawn
- :return:
- """
- pt1 = (float(sel_obj.options['xmin']), float(sel_obj.options['ymin']))
- pt2 = (float(sel_obj.options['xmax']), float(sel_obj.options['ymin']))
- pt3 = (float(sel_obj.options['xmax']), float(sel_obj.options['ymax']))
- pt4 = (float(sel_obj.options['xmin']), float(sel_obj.options['ymax']))
- hover_rect = Polygon([pt1, pt2, pt3, pt4])
- if self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
- hover_rect = hover_rect.buffer(-0.1)
- hover_rect = hover_rect.buffer(0.2)
- else:
- hover_rect = hover_rect.buffer(-0.00393)
- hover_rect = hover_rect.buffer(0.00787)
- # if color:
- # face = Color(color)
- # face.alpha = 0.2
- # outline = Color(color, alpha=0.8)
- # else:
- # face = Color(self.defaults['global_sel_fill'])
- # face.alpha = 0.2
- # outline = self.defaults['global_sel_line']
- if color:
- face = color[:-2] + str(hex(int(0.2 * 255)))[2:]
- outline = color[:-2] + str(hex(int(0.8 * 255)))[2:]
- else:
- face = self.defaults['global_sel_fill'][:-2] + str(hex(int(0.2 * 255)))[2:]
- outline = self.defaults['global_sel_line']
- self.hover_shapes.add(hover_rect, color=outline, face_color=face, update=True, layer=0, tolerance=None)
- if self.is_legacy is True:
- self.hover_shapes.redraw()
- def delete_selection_shape(self):
- self.move_tool.sel_shapes.clear()
- self.move_tool.sel_shapes.redraw()
- def draw_selection_shape(self, sel_obj, color=None):
- """
- :param sel_obj: the object for which the selection shape must be drawn
- :return:
- """
- pt1 = (float(sel_obj.options['xmin']), float(sel_obj.options['ymin']))
- pt2 = (float(sel_obj.options['xmax']), float(sel_obj.options['ymin']))
- pt3 = (float(sel_obj.options['xmax']), float(sel_obj.options['ymax']))
- pt4 = (float(sel_obj.options['xmin']), float(sel_obj.options['ymax']))
- sel_rect = Polygon([pt1, pt2, pt3, pt4])
- if self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
- sel_rect = sel_rect.buffer(-0.1)
- sel_rect = sel_rect.buffer(0.2)
- else:
- sel_rect = sel_rect.buffer(-0.00393)
- sel_rect = sel_rect.buffer(0.00787)
- # if color:
- # face = Color(color, alpha=0.2)
- # outline = Color(color, alpha=0.8)
- # else:
- # face = Color(self.defaults['global_sel_fill'], alpha=0.2)
- # outline = Color(self.defaults['global_sel_line'], alpha=0.8)
- if color:
- face = color[:-2] + str(hex(int(0.2 * 255)))[2:]
- outline = color[:-2] + str(hex(int(0.8 * 255)))[2:]
- else:
- face = self.defaults['global_sel_fill'][:-2] + str(hex(int(0.2 * 255)))[2:]
- outline = self.defaults['global_sel_line'][:-2] + str(hex(int(0.8 * 255)))[2:]
- self.sel_objects_list.append(self.move_tool.sel_shapes.add(sel_rect,
- color=outline,
- face_color=face,
- update=True,
- layer=0,
- tolerance=None))
- if self.is_legacy is True:
- self.move_tool.sel_shapes.redraw()
- def draw_moving_selection_shape(self, old_coords, coords, **kwargs):
- """
- :param old_coords: old coordinates
- :param coords: new coordinates
- :return:
- """
- if 'color' in kwargs:
- color = kwargs['color']
- else:
- color = self.defaults['global_sel_line']
- if 'face_color' in kwargs:
- face_color = kwargs['face_color']
- else:
- face_color = self.defaults['global_sel_fill']
- if 'face_alpha' in kwargs:
- face_alpha = kwargs['face_alpha']
- else:
- face_alpha = 0.3
- x0, y0 = old_coords
- x1, y1 = coords
- pt1 = (x0, y0)
- pt2 = (x1, y0)
- pt3 = (x1, y1)
- pt4 = (x0, y1)
- sel_rect = Polygon([pt1, pt2, pt3, pt4])
- # color_t = Color(face_color)
- # color_t.alpha = face_alpha
- color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
- self.move_tool.sel_shapes.add(sel_rect, color=color, face_color=color_t, update=True,
- layer=0, tolerance=None)
- if self.is_legacy is True:
- self.move_tool.sel_shapes.redraw()
- def on_file_new_click(self):
- if self.collection.get_list() and self.should_we_save:
- msgbox = QtWidgets.QMessageBox()
- # msgbox.setText("<B>Save changes ...</B>")
- msgbox.setText(_("There are files/objects opened in FlatCAM.\n"
- "Creating a New project will delete them.\n"
- "Do you want to Save the project?"))
- msgbox.setWindowTitle(_("Save changes"))
- msgbox.setWindowIcon(QtGui.QIcon('share/save_as.png'))
- bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
- bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
- bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.RejectRole)
- msgbox.setDefaultButton(bt_yes)
- msgbox.exec_()
- response = msgbox.clickedButton()
- if response == bt_yes:
- self.on_file_saveprojectas()
- elif response == bt_cancel:
- return
- elif response == bt_no:
- self.on_file_new()
- else:
- self.on_file_new()
- self.inform.emit('[success] %s...' %
- _("New Project created"))
- def on_file_new(self, cli=None):
- """
- Callback for menu item File->New. Returns the application to its
- startup state. This method is thread-safe.
- :return: None
- """
- self.report_usage("on_file_new")
- # Remove everything from memory
- App.log.debug("on_file_new()")
- if self.call_source != 'app':
- self.editor2object(cleanup=True)
- # ## EDITOR section
- self.geo_editor = FlatCAMGeoEditor(self, disabled=True)
- self.exc_editor = FlatCAMExcEditor(self)
- self.grb_editor = FlatCAMGrbEditor(self)
- # Clear pool
- self.clear_pool()
- for obj in self.collection.get_list():
- # delete shapes left drawn from mark shape_collections, if any
- if isinstance(obj, FlatCAMGerber):
- try:
- obj.mark_shapes.enabled = False
- obj.mark_shapes.clear(update=True)
- except AttributeError:
- pass
- # also delete annotation shapes, if any
- elif isinstance(obj, FlatCAMCNCjob):
- try:
- obj.annotation.enabled = False
- obj.annotation.clear(update=True)
- except AttributeError:
- pass
- # tcl needs to be reinitialized, otherwise old shell variables etc remains
- self.init_tcl()
- self.delete_selection_shape()
- self.collection.delete_all()
- self.setup_component_editor()
- # Clear project filename
- self.project_filename = None
- # Load the application defaults
- self.load_defaults(filename='current_defaults')
- # Re-fresh project options
- self.on_options_app2project()
- # Init Tools
- self.init_tools()
- if cli is None:
- # Close any Tabs opened in the Plot Tab Area section
- for index in range(self.ui.plot_tab_area.count()):
- self.ui.plot_tab_area.closeTab(index)
- # for whatever reason previous command does not close the last tab so I do it manually
- self.ui.plot_tab_area.closeTab(0)
- # # And then add again the Plot Area
- self.ui.plot_tab_area.addTab(self.ui.plot_tab, "Plot Area")
- self.ui.plot_tab_area.protectTab(0)
- # take the focus of the Notebook on Project Tab.
- self.ui.notebook.setCurrentWidget(self.ui.project_tab)
- self.set_ui_title(name=_("New Project - Not saved"))
- def obj_properties(self):
- self.report_usage("obj_properties()")
- self.properties_tool.run(toggle=False)
- def on_project_context_save(self):
- obj = self.collection.get_active()
- if type(obj) == FlatCAMGeometry:
- self.on_file_exportdxf()
- elif type(obj) == FlatCAMExcellon:
- self.on_file_saveexcellon()
- elif type(obj) == FlatCAMCNCjob:
- obj.on_exportgcode_button_click()
- elif type(obj) == FlatCAMGerber:
- self.on_file_savegerber()
- elif type(obj) == FlatCAMScript:
- self.on_file_savescript()
- elif type(obj) == FlatCAMDocument:
- self.on_file_savedocument()
- def obj_move(self):
- self.report_usage("obj_move()")
- self.move_tool.run(toggle=False)
- def on_fileopengerber(self, checked=None, name=None):
- """
- File menu callback for opening a Gerber.
- :return: None
- """
- self.report_usage("on_fileopengerber")
- App.log.debug("on_fileopengerber()")
- _filter_ = "Gerber Files (*.gbr *.ger *.gtl *.gbl *.gts *.gbs *.gtp *.gbp *.gto *.gbo *.gm1 *.gml *.gm3 *" \
- ".gko *.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim *.mil *.grb" \
- "*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb *.pho *.gdo *.art *.gbd *.gb*);;" \
- "Protel Files (*.gtl *.gbl *.gts *.gbs *.gto *.gbo *.gtp *.gbp *.gml *.gm1 *.gm3 *.gko);;" \
- "Eagle Files (*.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim " \
- "*.mil);;" \
- "OrCAD Files (*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb);;" \
- "Allegro Files (*.art);;" \
- "Mentor Files (*.pho *.gdo);;" \
- "All Files (*.*)"
- if name is None:
- try:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Gerber"),
- directory=self.get_last_folder(),
- filter=_filter_)
- except TypeError:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Gerber"), filter=_filter_)
- filenames = [str(filename) for filename in filenames]
- else:
- filenames = [name]
- self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
- "Canvas initialization finished in"), '%.2f' % self.used_time,
- _("Opening Gerber file.")),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- if len(filenames) == 0:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Open Gerber cancelled."))
- else:
- for filename in filenames:
- if filename != '':
- self.worker_task.emit({'fcn': self.open_gerber, 'params': [filename]})
- def on_fileopenexcellon(self, checked=None, name=None):
- """
- File menu callback for opening an Excellon file.
- :return: None
- """
- self.report_usage("on_fileopenexcellon")
- App.log.debug("on_fileopenexcellon()")
- _filter_ = "Excellon Files (*.drl *.txt *.xln *.drd *.tap *.exc *.ncd);;" \
- "All Files (*.*)"
- if name is None:
- try:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Excellon"),
- directory=self.get_last_folder(),
- filter=_filter_)
- except TypeError:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Excellon"), filter=_filter_)
- filenames = [str(filename) for filename in filenames]
- else:
- filenames = [str(name)]
- self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
- "Canvas initialization finished in"), '%.2f' % self.used_time,
- _("Opening Excellon file.")),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- if len(filenames) == 0:
- self.inform.emit('[WARNING_NOTCL]%s' %
- _(" Open Excellon cancelled."))
- else:
- for filename in filenames:
- if filename != '':
- self.worker_task.emit({'fcn': self.open_excellon, 'params': [filename]})
- def on_fileopengcode(self, checked=None, name=None):
- """
- File menu call back for opening gcode.
- :return: None
- """
- self.report_usage("on_fileopengcode")
- App.log.debug("on_fileopengcode()")
- # https://bobcadsupport.com/helpdesk/index.php?/Knowledgebase/Article/View/13/5/known-g-code-file-extensions
- _filter_ = "G-Code Files (*.txt *.nc *.ncc *.tap *.gcode *.cnc *.ecs *.fnc *.dnc *.ncg *.gc *.fan *.fgc" \
- " *.din *.xpi *.hnc *.h *.i *.ncp *.min *.gcd *.rol *.mpr *.ply *.out *.eia *.plt *.sbp *.mpf);;" \
- "All Files (*.*)"
- if name is None:
- try:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open G-Code"),
- directory=self.get_last_folder(),
- filter=_filter_)
- except TypeError:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open G-Code"), filter=_filter_)
- filenames = [str(filename) for filename in filenames]
- else:
- filenames = [name]
- self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
- "Canvas initialization finished in"), '%.2f' % self.used_time,
- _("Opening G-Code file.")),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- if len(filenames) == 0:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Open G-Code cancelled."))
- else:
- for filename in filenames:
- if filename != '':
- self.worker_task.emit({'fcn': self.open_gcode, 'params': [filename]})
- def on_file_openproject(self, checked=None):
- """
- File menu callback for opening a project.
- :return: None
- """
- self.report_usage("on_file_openproject")
- App.log.debug("on_file_openproject()")
- _filter_ = "FlatCAM Project (*.FlatPrj);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Project"),
- directory=self.get_last_folder(), filter=_filter_)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Project"), filter=_filter_)
- # The Qt methods above will return a QString which can cause problems later.
- # So far json.dump() will fail to serialize it.
- # TODO: Improve the serialization methods and remove this fix.
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Open Project cancelled."))
- else:
- # self.worker_task.emit({'fcn': self.open_project,
- # 'params': [filename]})
- # The above was failing because open_project() is not
- # thread safe. The new_project()
- self.open_project(filename)
- def on_file_openconfig(self, checked=None):
- """
- File menu callback for opening a config file.
- :return: None
- """
- self.report_usage("on_file_openconfig")
- App.log.debug("on_file_openconfig()")
- _filter_ = "FlatCAM Config (*.FlatConfig);;FlatCAM Config (*.json);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Configuration File"),
- directory=self.data_path, filter=_filter_)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Configuration File"),
- filter=_filter_)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Open Config cancelled."))
- else:
- self.open_config_file(filename)
- def on_file_exportsvg(self):
- """
- Callback for menu item File->Export SVG.
- :return: None
- """
- self.report_usage("on_file_exportsvg")
- App.log.debug("on_file_exportsvg()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected."))
- msg = _("Please Select a Geometry object to export")
- msgbox = QtWidgets.QMessageBox()
- msgbox.setInformativeText(msg)
- bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
- msgbox.setDefaultButton(bt_ok)
- msgbox.exec_()
- return
- # Check for more compatible types and add as required
- if (not isinstance(obj, FlatCAMGeometry)
- and not isinstance(obj, FlatCAMGerber)
- and not isinstance(obj, FlatCAMCNCjob)
- and not isinstance(obj, FlatCAMExcellon)):
- msg = '[ERROR_NOTCL] %s' % \
- _("Only Geometry, Gerber and CNCJob objects can be used.")
- msgbox = QtWidgets.QMessageBox()
- msgbox.setInformativeText(msg)
- bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
- msgbox.setDefaultButton(bt_ok)
- msgbox.exec_()
- return
- name = obj.options["name"]
- _filter = "SVG File (*.svg);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption=_("Export SVG"),
- directory=self.get_last_save_folder() + '/' + str(name),
- filter=_filter)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG"), filter=_filter)
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL]%s' %
- _(" Export SVG cancelled."))
- return
- else:
- self.export_svg(name, filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("SVG", filename)
- self.file_saved.emit("SVG", filename)
- def on_file_exportpng(self):
- self.report_usage("on_file_exportpng")
- App.log.debug("on_file_exportpng()")
- self.date = str(datetime.today()).rpartition('.')[0]
- self.date = ''.join(c for c in self.date if c not in ':-')
- self.date = self.date.replace(' ', '_')
- image = _screenshot()
- data = np.asarray(image)
- if not data.ndim == 3 and data.shape[-1] in (3, 4):
- self.inform.emit('[[WARNING_NOTCL]] %s' %
- _('Data must be a 3D array with last dimension 3 or 4'))
- return
- filter_ = "PNG File (*.png);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption=_("Export PNG Image"),
- directory=self.get_last_save_folder() + '/png_' + self.date,
- filter=filter_)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export PNG Image"), filter=filter_)
- filename = str(filename)
- if filename == "":
- self.inform.emit(_("Export PNG cancelled."))
- return
- else:
- write_png(filename, data)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("png", filename)
- self.file_saved.emit("png", filename)
- def on_file_savegerber(self):
- """
- Callback for menu item in Project context menu.
- :return: None
- """
- self.report_usage("on_file_savegerber")
- App.log.debug("on_file_savegerber()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected. Please select an Gerber object to export."))
- return
- # Check for more compatible types and add as required
- if not isinstance(obj, FlatCAMGerber):
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Only Gerber objects can be saved as Gerber files..."))
- return
- name = self.collection.get_active().options["name"]
- _filter = "Gerber File (*.GBR);;Gerber File (*.GRB);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption="Save Gerber source file",
- directory=self.get_last_save_folder() + '/' + name,
- filter=_filter)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Gerber source file"), filter=_filter)
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Save Gerber source file cancelled."))
- return
- else:
- self.save_source_file(name, filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("Gerber", filename)
- self.file_saved.emit("Gerber", filename)
- def on_file_savescript(self):
- """
- Callback for menu item in Project context menu.
- :return: None
- """
- self.report_usage("on_file_savescript")
- App.log.debug("on_file_savescript()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected. Please select an Script object to export."))
- return
- # Check for more compatible types and add as required
- if not isinstance(obj, FlatCAMScript):
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Only Script objects can be saved as TCL Script files..."))
- return
- name = self.collection.get_active().options["name"]
- _filter = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption="Save Script source file",
- directory=self.get_last_save_folder() + '/' + name,
- filter=_filter)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Script source file"), filter=_filter)
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Save Script source file cancelled."))
- return
- else:
- self.save_source_file(name, filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("Script", filename)
- self.file_saved.emit("Script", filename)
- def on_file_savedocument(self):
- """
- Callback for menu item in Project context menu.
- :return: None
- """
- self.report_usage("on_file_savedocument")
- App.log.debug("on_file_savedocument()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected. Please select an Document object to export."))
- return
- # Check for more compatible types and add as required
- if not isinstance(obj, FlatCAMScript):
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Only Document objects can be saved as Document files..."))
- return
- name = self.collection.get_active().options["name"]
- _filter = "FlatCAM Documents (*.FlatDoc);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption="Save Document source file",
- directory=self.get_last_save_folder() + '/' + name,
- filter=_filter)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Document source file"), filter=_filter)
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Save Document source file cancelled."))
- return
- else:
- self.save_source_file(name, filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("Document", filename)
- self.file_saved.emit("Document", filename)
- def on_file_saveexcellon(self):
- """
- Callback for menu item in project context menu.
- :return: None
- """
- self.report_usage("on_file_saveexcellon")
- App.log.debug("on_file_saveexcellon()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected. Please select an Excellon object to export."))
- return
- # Check for more compatible types and add as required
- if not isinstance(obj, FlatCAMExcellon):
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Only Excellon objects can be saved as Excellon files..."))
- return
- name = self.collection.get_active().options["name"]
- _filter = "Excellon File (*.DRL);;Excellon File (*.TXT);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption=_("Save Excellon source file"),
- directory=self.get_last_save_folder() + '/' + name,
- filter=_filter)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Excellon source file"), filter=_filter)
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Saving Excellon source file cancelled."))
- return
- else:
- self.save_source_file(name, filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("Excellon", filename)
- self.file_saved.emit("Excellon", filename)
- def on_file_exportexcellon(self):
- """
- Callback for menu item File->Export->Excellon.
- :return: None
- """
- self.report_usage("on_file_exportexcellon")
- App.log.debug("on_file_exportexcellon()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected. Please Select an Excellon object to export."))
- return
- # Check for more compatible types and add as required
- if not isinstance(obj, FlatCAMExcellon):
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Only Excellon objects can be saved as Excellon files..."))
- return
- name = self.collection.get_active().options["name"]
- _filter = "Excellon File (*.DRL);;Excellon File (*.TXT);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption=_("Export Excellon"),
- directory=self.get_last_save_folder() + '/' + name,
- filter=_filter)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export Excellon"), filter=_filter)
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Export Excellon cancelled."))
- return
- else:
- self.export_excellon(name, filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("Excellon", filename)
- self.file_saved.emit("Excellon", filename)
- def on_file_exportgerber(self):
- """
- Callback for menu item File->Export->Gerber.
- :return: None
- """
- self.report_usage("on_file_exportgerber")
- App.log.debug("on_file_exportgerber()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected. Please Select an Gerber object to export."))
- return
- # Check for more compatible types and add as required
- if not isinstance(obj, FlatCAMGerber):
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed. Only Gerber objects can be saved as Gerber files..."))
- return
- name = self.collection.get_active().options["name"]
- _filter_ = "Gerber File (*.GBR);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption=_("Export Gerber"),
- directory=self.get_last_save_folder() + '/' + name,
- filter=_filter_)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export Gerber"), filter=_filter_)
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Export Gerber cancelled."))
- return
- else:
- self.export_gerber(name, filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("Gerber", filename)
- self.file_saved.emit("Gerber", filename)
- def on_file_exportdxf(self):
- """
- Callback for menu item File->Export DXF.
- :return: None
- """
- self.report_usage("on_file_exportdxf")
- App.log.debug("on_file_exportdxf()")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected."))
- msg = _("Please Select a Geometry object to export")
- msgbox = QtWidgets.QMessageBox()
- msgbox.setInformativeText(msg)
- bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
- msgbox.setDefaultButton(bt_ok)
- msgbox.exec_()
- return
- # Check for more compatible types and add as required
- if not isinstance(obj, FlatCAMGeometry):
- msg = '[ERROR_NOTCL] %s' % \
- _("Only Geometry objects can be used.")
- msgbox = QtWidgets.QMessageBox()
- msgbox.setInformativeText(msg)
- bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
- msgbox.setDefaultButton(bt_ok)
- msgbox.exec_()
- return
- name = self.collection.get_active().options["name"]
- _filter_ = "DXF File (*.DXF);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption=_("Export DXF"),
- directory=self.get_last_save_folder() + '/' + name,
- filter=_filter_)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export DXF"),
- filter=_filter_)
- filename = str(filename)
- if filename == "":
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Export DXF cancelled."))
- return
- else:
- self.export_dxf(name, filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("DXF", filename)
- self.file_saved.emit("DXF", filename)
- def on_file_importsvg(self, type_of_obj):
- """
- Callback for menu item File->Import SVG.
- :param type_of_obj: to import the SVG as Geometry or as Gerber
- :type type_of_obj: str
- :return: None
- """
- self.report_usage("on_file_importsvg")
- App.log.debug("on_file_importsvg()")
- _filter_ = "SVG File (*.svg);;All Files (*.*)"
- try:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import SVG"),
- directory=self.get_last_folder(), filter=_filter_)
- except TypeError:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import SVG"),
- filter=_filter_)
- if type_of_obj is not "geometry" and type_of_obj is not "gerber":
- type_of_obj = "geometry"
- filenames = [str(filename) for filename in filenames]
- if len(filenames) == 0:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Open SVG cancelled."))
- else:
- for filename in filenames:
- if filename != '':
- self.worker_task.emit({'fcn': self.import_svg,
- 'params': [filename, type_of_obj]})
- def on_file_importdxf(self, type_of_obj):
- """
- Callback for menu item File->Import DXF.
- :param type_of_obj: to import the DXF as Geometry or as Gerber
- :type type_of_obj: str
- :return: None
- """
- self.report_usage("on_file_importdxf")
- App.log.debug("on_file_importdxf()")
- _filter_ = "DXF File (*.DXF);;All Files (*.*)"
- try:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import DXF"),
- directory=self.get_last_folder(),
- filter=_filter_)
- except TypeError:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import DXF"),
- filter=_filter_)
- if type_of_obj is not "geometry" and type_of_obj is not "gerber":
- type_of_obj = "geometry"
- filenames = [str(filename) for filename in filenames]
- if len(filenames) == 0:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Open DXF cancelled."))
- else:
- for filename in filenames:
- if filename != '':
- self.worker_task.emit({'fcn': self.import_dxf,
- 'params': [filename, type_of_obj]})
- # ###############################################################################################################
- # ### The following section has the functions that are displayed and call the Editor tab CNCJob Tab #############
- # ###############################################################################################################
- def init_code_editor(self, name):
- self.ui.text_editor_tab = TextEditor(app=self)
- # add the tab if it was closed
- self.ui.plot_tab_area.addTab(self.ui.text_editor_tab, '%s' % name)
- self.ui.text_editor_tab.setObjectName('text_editor_tab')
- # delete the absolute and relative position and messages in the infobar
- self.ui.position_label.setText("")
- self.ui.rel_position_label.setText("")
- # first clear previous text in text editor (if any)
- self.ui.text_editor_tab.code_editor.clear()
- self.ui.text_editor_tab.code_editor.setReadOnly(False)
- self.toggle_codeeditor = True
- self.ui.text_editor_tab.code_editor.completer_enable = False
- self.ui.text_editor_tab.buttonRun.hide()
- # make sure to keep a reference to the code editor
- self.reference_code_editor = self.ui.text_editor_tab.code_editor
- # Switch plot_area to CNCJob tab
- self.ui.plot_tab_area.setCurrentWidget(self.ui.text_editor_tab)
- def on_view_source(self):
- self.inform.emit('%s' %
- _("Viewing the source code of the selected object."))
- self.proc_container.view.set_busy(_("Loading..."))
- try:
- obj = self.collection.get_active()
- except Exception as e:
- log.debug("App.on_view_source() --> %s" % str(e))
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Select an Gerber or Excellon file to view it's source file."))
- return 'fail'
- if obj.kind == 'gerber':
- flt = "Gerber Files (*.GBR);;All Files (*.*)"
- elif obj.kind == 'excellon':
- flt = "Excellon Files (*.DRL);;All Files (*.*)"
- elif obj.kind == 'cncjob':
- "GCode Files (*.NC);;All Files (*.*)"
- else:
- flt = "All Files (*.*)"
- self.source_editor_tab = TextEditor(app=self)
- # add the tab if it was closed
- self.ui.plot_tab_area.addTab(self.source_editor_tab, '%s' % _("Source Editor"))
- self.source_editor_tab.setObjectName('source_editor_tab')
- # delete the absolute and relative position and messages in the infobar
- self.ui.position_label.setText("")
- self.ui.rel_position_label.setText("")
- # first clear previous text in text editor (if any)
- self.source_editor_tab.code_editor.clear()
- self.source_editor_tab.code_editor.setReadOnly(False)
- self.source_editor_tab.code_editor.completer_enable = False
- self.source_editor_tab.buttonRun.hide()
- # Switch plot_area to CNCJob tab
- self.ui.plot_tab_area.setCurrentWidget(self.source_editor_tab)
- self.source_editor_tab.buttonOpen.clicked.disconnect()
- self.source_editor_tab.buttonOpen.clicked.connect(lambda: self.source_editor_tab.handleOpen(filt=flt))
- self.source_editor_tab.buttonSave.clicked.disconnect()
- self.source_editor_tab.buttonSave.clicked.connect(lambda: self.source_editor_tab.handleSaveGCode(filt=flt))
- # then append the text from GCode to the text editor
- if obj.kind == 'cncjob':
- try:
- file = obj.export_gcode(
- preamble=self.defaults["cncjob_prepend"],
- postamble=self.defaults["cncjob_append"],
- to_file=True)
- if file == 'fail':
- return 'fail'
- except AttributeError:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("There is no selected object for which to see it's source file code."))
- return 'fail'
- else:
- try:
- file = StringIO(obj.source_file)
- except AttributeError:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("There is no selected object for which to see it's source file code."))
- return 'fail'
- self.source_editor_tab.t_frame.hide()
- try:
- for line in file:
- QtWidgets.QApplication.processEvents()
- proc_line = str(line).strip('\n')
- self.source_editor_tab.code_editor.append(proc_line)
- except Exception as e:
- log.debug('App.on_view_source() -->%s' % str(e))
- self.inform.emit('[ERROR] %s: %s' %
- (_('Failed to load the source code for the selected object'), str(e)))
- return
- self.source_editor_tab.handleTextChanged()
- self.source_editor_tab.t_frame.show()
- self.source_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.Start)
- self.proc_container.view.set_idle()
- # self.ui.show()
- def on_toggle_code_editor(self):
- self.report_usage("on_toggle_code_editor()")
- if self.toggle_codeeditor is False:
- self.init_code_editor(name=_("Code Editor"))
- self.ui.text_editor_tab.buttonOpen.clicked.disconnect()
- self.ui.text_editor_tab.buttonOpen.clicked.connect(lambda: self.ui.text_editor_tab.handleOpen())
- self.ui.text_editor_tab.buttonSave.clicked.disconnect()
- self.ui.text_editor_tab.buttonSave.clicked.connect(lambda: self.ui.text_editor_tab.handleSaveGCode())
- else:
- for idx in range(self.ui.plot_tab_area.count()):
- if self.ui.plot_tab_area.widget(idx).objectName() == "text_editor_tab":
- self.ui.plot_tab_area.closeTab(idx)
- break
- self.toggle_codeeditor = False
- def on_filenewscript(self, silent=False, name=None, text=None):
- """
- Will create a new script file and open it in the Code Editor
- :param silent: if True will not display status messages
- :return: None
- """
- if silent is False:
- self.inform.emit('[success] %s' %
- _("New TCL script file created in Code Editor."))
- # delete the absolute and relative position and messages in the infobar
- self.ui.position_label.setText("")
- self.ui.rel_position_label.setText("")
- if name is not None:
- self.new_script_object(name=name, text=text)
- else:
- self.new_script_object(text=text)
- # script_text = script_obj.source_file
- #
- # self.proc_container.view.set_busy(_("Loading..."))
- # script_obj.script_editor_tab.t_frame.hide()
- #
- # script_obj.script_editor_tab.t_frame.show()
- # self.proc_container.view.set_idle()
- def on_fileopenscript(self, name=None, silent=False):
- """
- Will open a Tcl script file into the Code Editor
- :param silent: if True will not display status messages
- :param name: name of a Tcl script file to open
- :return:
- """
- self.report_usage("on_fileopenscript")
- App.log.debug("on_fileopenscript()")
- _filter_ = "TCL script (*.FlatScript);;TCL script (*.TCL);;TCL script (*.TXT);;All Files (*.*)"
- if name:
- filenames = [name]
- else:
- try:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(
- caption=_("Open TCL script"), directory=self.get_last_folder(), filter=_filter_)
- except TypeError:
- filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open TCL script"), filter=_filter_)
- if len(filenames) == 0:
- if silent is False:
- self.inform.emit('[WARNING_NOTCL] %s' % _("Open TCL script cancelled."))
- else:
- for filename in filenames:
- if filename != '':
- self.worker_task.emit({'fcn': self.open_script, 'params': [filename]})
- def on_filerunscript(self, name=None, silent=False):
- """
- File menu callback for loading and running a TCL script.
- :param silent: if True will not display status messages
- :param name: name of a Tcl script file to be run by FlatCAM
- :return: None
- """
- self.report_usage("on_filerunscript")
- App.log.debug("on_file_runscript()")
- if name:
- filename = name
- if self.cmd_line_headless != 1:
- self.splash.showMessage('%s: %ssec\n%s' %
- (_("Canvas initialization started.\n"
- "Canvas initialization finished in"), '%.2f' % self.used_time,
- _("Executing FlatCAMScript file.")
- ),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- else:
- _filter_ = "TCL script (*.FlatScript);;TCL script (*.TCL);;TCL script (*.TXT);;All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Run TCL script"),
- directory=self.get_last_folder(), filter=_filter_)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Run TCL script"), filter=_filter_)
- # The Qt methods above will return a QString which can cause problems later.
- # So far json.dump() will fail to serialize it.
- # TODO: Improve the serialization methods and remove this fix.
- filename = str(filename)
- if filename == "":
- if silent is False:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Run TCL script cancelled."))
- else:
- if self.cmd_line_headless != 1:
- if self.ui.shell_dock.isHidden():
- self.ui.shell_dock.show()
- try:
- with open(filename, "r") as tcl_script:
- cmd_line_shellfile_content = tcl_script.read()
- if self.cmd_line_headless != 1:
- self.shell._sysShell.exec_command(cmd_line_shellfile_content)
- else:
- self.shell._sysShell.exec_command(cmd_line_shellfile_content, no_echo=True)
- if silent is False:
- self.inform.emit('[success] %s' %
- _("TCL script file opened in Code Editor and executed."))
- except Exception as e:
- log.debug("App.on_filerunscript() -> %s" % str(e))
- sys.exit(2)
- def on_file_saveproject(self, silent=False):
- """
- Callback for menu item File->Save Project. Saves the project to
- ``self.project_filename`` or calls ``self.on_file_saveprojectas()``
- if set to None. The project is saved by calling ``self.save_project()``.
- :param silent: if True will not display status messages
- :return: None
- """
- self.report_usage("on_file_saveproject")
- if self.project_filename is None:
- self.on_file_saveprojectas()
- else:
- self.worker_task.emit({'fcn': self.save_project,
- 'params': [self.project_filename, silent]})
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("project", self.project_filename)
- self.file_saved.emit("project", self.project_filename)
- self.set_ui_title(name=self.project_filename)
- self.should_we_save = False
- def on_file_saveprojectas(self, make_copy=False, use_thread=True, quit_action=False):
- """
- Callback for menu item File->Save Project As... Opens a file
- chooser and saves the project to the given file via
- ``self.save_project()``.
- :param make_copy if to be create a copy of the project; boolean
- :param use_thread: if to be run in a separate thread; boolean
- :param quit_action: if to be followed by quiting the application; boolean
- :return: None
- """
- self.report_usage("on_file_saveprojectas")
- self.date = str(datetime.today()).rpartition('.')[0]
- self.date = ''.join(c for c in self.date if c not in ':-')
- self.date = self.date.replace(' ', '_')
- filter_ = "FlatCAM Project (*.FlatPrj);; All Files (*.*)"
- try:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(
- caption=_("Save Project As ..."),
- directory=_('{l_save}/Project_{date}').format(l_save=str(self.get_last_save_folder()), date=self.date),
- filter=filter_)
- except TypeError:
- filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Project As ..."), filter=filter_)
- filename = str(filename)
- if filename == '':
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Save Project cancelled."))
- return
- try:
- f = open(filename, 'r')
- f.close()
- except IOError:
- pass
- if use_thread is True:
- self.worker_task.emit({'fcn': self.save_project,
- 'params': [filename, quit_action]})
- else:
- self.save_project(filename, quit_action)
- # self.save_project(filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("project", filename)
- self.file_saved.emit("project", filename)
- if not make_copy:
- self.project_filename = filename
- self.set_ui_title(name=self.project_filename)
- self.should_we_save = False
- def export_svg(self, obj_name, filename, scale_factor=0.00):
- """
- Exports a Geometry Object to an SVG file.
- :param obj_name: the name of the FlatCAM object to be saved as SVG
- :param filename: Path to the SVG file to save to.
- :param scale_factor: factor by which to change/scale the thickness of the features
- :return:
- """
- self.report_usage("export_svg()")
- if filename is None:
- filename = self.defaults["global_last_save_folder"]
- self.log.debug("export_svg()")
- try:
- obj = self.collection.get_by_name(str(obj_name))
- except:
- # TODO: The return behavior has not been established... should raise exception?
- return "Could not retrieve object: %s" % obj_name
- with self.proc_container.new(_("Exporting SVG")) as proc:
- exported_svg = obj.export_svg(scale_factor=scale_factor)
- # Determine bounding area for svg export
- bounds = obj.bounds()
- size = obj.size()
- # Convert everything to strings for use in the xml doc
- svgwidth = str(size[0])
- svgheight = str(size[1])
- minx = str(bounds[0])
- miny = str(bounds[1] - size[1])
- uom = obj.units.lower()
- # Add a SVG Header and footer to the svg output from shapely
- # The transform flips the Y Axis so that everything renders
- # properly within svg apps such as inkscape
- svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
- 'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
- svg_header += 'width="' + svgwidth + uom + '" '
- svg_header += 'height="' + svgheight + uom + '" '
- svg_header += 'viewBox="' + minx + ' ' + miny + ' ' + svgwidth + ' ' + svgheight + '">'
- svg_header += '<g transform="scale(1,-1)">'
- svg_footer = '</g> </svg>'
- svg_elem = svg_header + exported_svg + svg_footer
- # Parse the xml through a xml parser just to add line feeds
- # and to make it look more pretty for the output
- svgcode = parse_xml_string(svg_elem)
- try:
- with open(filename, 'w') as fp:
- fp.write(svgcode.toprettyxml())
- except PermissionError:
- self.inform.emit('[WARNING] %s' %
- _("Permission denied, saving not possible.\n"
- "Most likely another app is holding the file open and not accessible."))
- return 'fail'
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("SVG", filename)
- self.file_saved.emit("SVG", filename)
- self.inform.emit('[success] %s: %s' %
- (_("SVG file exported to"), filename))
- def export_svg_negative(self, obj_name, box_name, filename, boundary, scale_factor=0.00, use_thread=True):
- """
- Exports a Geometry Object to an SVG file in negative.
- :param obj_name: the name of the FlatCAM object to be saved as SVG
- :param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
- :param filename: Path to the SVG file to save to.
- :param boundary: thickness of a black border to surround all the features
- :param scale_factor: factor by which to change/scale the thickness of the features
- :param use_thread: if to be run in a separate thread; boolean
- :return:
- """
- self.report_usage("export_negative()")
- if filename is None:
- filename = self.defaults["global_last_save_folder"]
- self.log.debug("export_svg() negative")
- try:
- obj = self.collection.get_by_name(str(obj_name))
- except:
- # TODO: The return behavior has not been established... should raise exception?
- return "Could not retrieve object: %s" % obj_name
- try:
- box = self.collection.get_by_name(str(box_name))
- except:
- # TODO: The return behavior has not been established... should raise exception?
- return "Could not retrieve object: %s" % box_name
- if box is None:
- self.inform.emit('[WARNING_NOTCL] %s: %s' %
- (_("No object Box. Using instead"), obj))
- box = obj
- def make_negative_film():
- exported_svg = obj.export_svg(scale_factor=scale_factor)
- # Determine bounding area for svg export
- bounds = box.bounds()
- size = box.size()
- uom = obj.units.lower()
- # Convert everything to strings for use in the xml doc
- svgwidth = str(size[0] + (2 * boundary))
- svgheight = str(size[1] + (2 * boundary))
- minx = str(bounds[0] - boundary)
- miny = str(bounds[1] + boundary + size[1])
- miny_rect = str(bounds[1] - boundary)
- # Add a SVG Header and footer to the svg output from shapely
- # The transform flips the Y Axis so that everything renders
- # properly within svg apps such as inkscape
- svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
- 'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
- svg_header += 'width="' + svgwidth + uom + '" '
- svg_header += 'height="' + svgheight + uom + '" '
- svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
- svg_header += '>'
- svg_header += '<g transform="scale(1,-1)">'
- svg_footer = '</g> </svg>'
- # Change the attributes of the exported SVG
- # We don't need stroke-width - wrong, we do when we have lines with certain width
- # We set opacity to maximum
- # We set the color to WHITE
- root = ET.fromstring(exported_svg)
- for child in root:
- child.set('fill', '#FFFFFF')
- child.set('opacity', '1.0')
- child.set('stroke', '#FFFFFF')
- # first_svg_elem = 'rect x="' + minx + '" ' + 'y="' + miny_rect + '" '
- # first_svg_elem += 'width="' + svgwidth + '" ' + 'height="' + svgheight + '" '
- # first_svg_elem += 'fill="#000000" opacity="1.0" stroke-width="0.0"'
- first_svg_elem_tag = 'rect'
- first_svg_elem_attribs = {
- 'x': minx,
- 'y': miny_rect,
- 'width': svgwidth,
- 'height': svgheight,
- 'id': 'neg_rect',
- 'style': 'fill:#000000;opacity:1.0;stroke-width:0.0'
- }
- root.insert(0, ET.Element(first_svg_elem_tag, first_svg_elem_attribs))
- exported_svg = ET.tostring(root)
- svg_elem = svg_header + str(exported_svg) + svg_footer
- # Parse the xml through a xml parser just to add line feeds
- # and to make it look more pretty for the output
- doc = parse_xml_string(svg_elem)
- try:
- with open(filename, 'w') as fp:
- fp.write(doc.toprettyxml())
- except PermissionError:
- self.inform.emit('[WARNING] %s' %
- _("Permission denied, saving not possible.\n"
- "Most likely another app is holding the file open and not accessible."))
- return 'fail'
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("SVG", filename)
- self.file_saved.emit("SVG", filename)
- self.inform.emit('[success] %s: %s' %
- (_("SVG file exported to"), filename))
- if use_thread is True:
- proc = self.proc_container.new(_("Generating Film ... Please wait."))
- def job_thread_film(app_obj):
- try:
- make_negative_film()
- except Exception as e:
- proc.done()
- return
- proc.done()
- self.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
- else:
- make_negative_film()
- def export_svg_positive(self, obj_name, box_name, filename, scale_factor=0.00, use_thread=True):
- """
- Exports a Geometry Object to an SVG file in positive black.
- :param obj_name: the name of the FlatCAM object to be saved as SVG
- :param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
- :param filename: Path to the SVG file to save to.
- :param scale_factor: factor by which to change/scale the thickness of the features
- :param use_thread: if to be run in a separate thread; boolean
- :return:
- """
- self.report_usage("export_svg_positive()")
- if filename is None:
- filename = self.defaults["global_last_save_folder"]
- self.log.debug("export_svg() black")
- try:
- obj = self.collection.get_by_name(str(obj_name))
- except:
- # TODO: The return behavior has not been established... should raise exception?
- return "Could not retrieve object: %s" % obj_name
- try:
- box = self.collection.get_by_name(str(box_name))
- except:
- # TODO: The return behavior has not been established... should raise exception?
- return "Could not retrieve object: %s" % box_name
- if box is None:
- self.inform.emit('[WARNING_NOTCL] %s: %s' %
- (_("No object Box. Using instead"), obj))
- box = obj
- def make_positive_film():
- exported_svg = obj.export_svg(scale_factor=scale_factor)
- self.progress.emit(40)
- # Change the attributes of the exported SVG
- # We don't need stroke-width
- # We set opacity to maximum
- # We set the colour to WHITE
- root = ET.fromstring(exported_svg)
- for child in root:
- child.set('fill', str(self.defaults['tools_film_color']))
- child.set('opacity', '1.0')
- child.set('stroke', str(self.defaults['tools_film_color']))
- exported_svg = ET.tostring(root)
- # Determine bounding area for svg export
- bounds = box.bounds()
- size = box.size()
- # This contain the measure units
- uom = obj.units.lower()
- # Define a boundary around SVG of about 1.0mm (~39mils)
- if uom in "mm":
- boundary = 1.0
- else:
- boundary = 0.0393701
- self.progress.emit(80)
- # Convert everything to strings for use in the xml doc
- svgwidth = str(size[0] + (2 * boundary))
- svgheight = str(size[1] + (2 * boundary))
- minx = str(bounds[0] - boundary)
- miny = str(bounds[1] + boundary + size[1])
- self.log.debug(minx)
- self.log.debug(miny)
- # Add a SVG Header and footer to the svg output from shapely
- # The transform flips the Y Axis so that everything renders
- # properly within svg apps such as inkscape
- svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
- 'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
- svg_header += 'width="' + svgwidth + uom + '" '
- svg_header += 'height="' + svgheight + uom + '" '
- svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
- svg_header += '>'
- svg_header += '<g transform="scale(1,-1)">'
- svg_footer = '</g> </svg>'
- svg_elem = str(svg_header) + str(exported_svg) + str(svg_footer)
- self.progress.emit(90)
- # Parse the xml through a xml parser just to add line feeds
- # and to make it look more pretty for the output
- doc = parse_xml_string(svg_elem)
- try:
- with open(filename, 'w') as fp:
- fp.write(doc.toprettyxml())
- except PermissionError:
- self.inform.emit('[WARNING] %s' %
- _("Permission denied, saving not possible.\n"
- "Most likely another app is holding the file open and not accessible."))
- return 'fail'
- self.progress.emit(100)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("SVG", filename)
- self.file_saved.emit("SVG", filename)
- self.inform.emit('[success] %s: %s' %
- (_("SVG file exported to"), filename))
- if use_thread is True:
- proc = self.proc_container.new(_("Generating Film ... Please wait."))
- def job_thread_film(app_obj):
- try:
- make_positive_film()
- except Exception as e:
- proc.done()
- return
- proc.done()
- self.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
- else:
- make_positive_film()
- def save_source_file(self, obj_name, filename, use_thread=True):
- """
- Exports a FlatCAM Object to an Gerber/Excellon file.
- :param obj_name: the name of the FlatCAM object for which to save it's embedded source file
- :param filename: Path to the Gerber file to save to.
- :param use_thread: if to be run in a separate thread
- :return:
- """
- self.report_usage("save source file()")
- if filename is None:
- filename = self.defaults["global_last_save_folder"]
- self.log.debug("save source file()")
- obj = self.collection.get_by_name(obj_name)
- file_string = StringIO(obj.source_file)
- time_string = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
- try:
- with open(filename, 'w') as file:
- file.writelines('G04*\n')
- file.writelines('G04 %s (RE)GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s*\n' %
- (obj.kind.upper(), str(self.version), str(self.version_date)))
- file.writelines('G04 Filename: %s*\n' % str(obj_name))
- file.writelines('G04 Created on : %s*\n' % time_string)
- for line in file_string:
- file.writelines(line)
- except PermissionError:
- self.inform.emit('[WARNING] %s' %
- _("Permission denied, saving not possible.\n"
- "Most likely another app is holding the file open and not accessible."))
- return 'fail'
- def export_excellon(self, obj_name, filename, local_use=None, use_thread=True):
- """
- Exports a Excellon Object to an Excellon file.
- :param obj_name: the name of the FlatCAM object to be saved as Excellon
- :param filename: Path to the Excellon file to save to.
- :param use_thread: if to be run in a separate thread
- :return:
- """
- self.report_usage("export_excellon()")
- if filename is None:
- filename = self.defaults["global_last_save_folder"]
- self.log.debug("export_excellon()")
- format_exc = ';FILE_FORMAT=%d:%d\n' % (self.defaults["excellon_exp_integer"],
- self.defaults["excellon_exp_decimals"]
- )
- if local_use is None:
- try:
- obj = self.collection.get_by_name(str(obj_name))
- except:
- # TODO: The return behavior has not been established... should raise exception?
- return "Could not retrieve object: %s" % obj_name
- else:
- obj = local_use
- # updated units
- eunits = self.defaults["excellon_exp_units"]
- ewhole = self.defaults["excellon_exp_integer"]
- efract = self.defaults["excellon_exp_decimals"]
- ezeros = self.defaults["excellon_exp_zeros"]
- eformat = self.defaults["excellon_exp_format"]
- slot_type = self.defaults["excellon_exp_slot_type"]
- fc_units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
- if fc_units == 'MM':
- factor = 1 if eunits == 'METRIC' else 0.03937
- else:
- factor = 25.4 if eunits == 'METRIC' else 1
- def make_excellon():
- try:
- time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
- header = 'M48\n'
- header += ';EXCELLON GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \
- (str(self.version), str(self.version_date))
- header += ';Filename: %s' % str(obj_name) + '\n'
- header += ';Created on : %s' % time_str + '\n'
- if eformat == 'dec':
- has_slots, excellon_code = obj.export_excellon(ewhole, efract, factor=factor, slot_type=slot_type)
- header += eunits + '\n'
- for tool in obj.tools:
- if eunits == 'METRIC':
- header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
- tool=str(tool),
- dec=2)
- else:
- header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
- tool=str(tool),
- dec=4)
- else:
- if ezeros == 'LZ':
- has_slots, excellon_code = obj.export_excellon(ewhole, efract,
- form='ndec', e_zeros='LZ', factor=factor,
- slot_type=slot_type)
- header += '%s,%s\n' % (eunits, 'LZ')
- header += format_exc
- for tool in obj.tools:
- if eunits == 'METRIC':
- header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
- tool=str(tool),
- dec=2)
- else:
- header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
- tool=str(tool),
- dec=4)
- else:
- has_slots, excellon_code = obj.export_excellon(ewhole, efract,
- form='ndec', e_zeros='TZ', factor=factor,
- slot_type=slot_type)
- header += '%s,%s\n' % (eunits, 'TZ')
- header += format_exc
- for tool in obj.tools:
- if eunits == 'METRIC':
- header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
- tool=str(tool),
- dec=2)
- else:
- header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
- tool=str(tool),
- dec=4)
- header += '%\n'
- footer = 'M30\n'
- exported_excellon = header
- exported_excellon += excellon_code
- exported_excellon += footer
- if local_use is None:
- try:
- with open(filename, 'w') as fp:
- fp.write(exported_excellon)
- except PermissionError:
- self.inform.emit('[WARNING] %s' %
- _("Permission denied, saving not possible.\n"
- "Most likely another app is holding the file open and not accessible."))
- return 'fail'
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("Excellon", filename)
- self.file_saved.emit("Excellon", filename)
- self.inform.emit('[success] %s: %s' %
- (_("Excellon file exported to"), filename))
- else:
- return exported_excellon
- except Exception as e:
- log.debug("App.export_excellon.make_excellon() --> %s" % str(e))
- return 'fail'
- if use_thread is True:
- with self.proc_container.new(_("Exporting Excellon")) as proc:
- def job_thread_exc(app_obj):
- ret = make_excellon()
- if ret == 'fail':
- self.inform.emit('[ERROR_NOTCL] %s' %
- _('Could not export Excellon file.'))
- return
- self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]})
- else:
- ret = make_excellon()
- if ret == 'fail':
- self.inform.emit('[ERROR_NOTCL] %s' %
- _('Could not export Excellon file.'))
- return 'fail'
- if local_use is not None:
- return ret
- def export_gerber(self, obj_name, filename, local_use=None, use_thread=True):
- """
- Exports a Gerber Object to an Gerber file.
- :param obj_name: the name of the FlatCAM object to be saved as Gerber
- :param filename: Path to the Gerber file to save to.
- :param local_use: if the Gerber code is to be saved to a file (None) or used within FlatCAM.
- When not None, the value will be the actual Gerber object for which to create the Gerber code
- :param use_thread: if to be run in a separate thread
- :return:
- """
- self.report_usage("export_gerber()")
- if filename is None:
- filename = self.defaults["global_last_save_folder"]
- self.log.debug("export_gerber()")
- if local_use is None:
- try:
- obj = self.collection.get_by_name(str(obj_name))
- except:
- # TODO: The return behavior has not been established... should raise exception?
- return "Could not retrieve object: %s" % obj_name
- else:
- obj = local_use
- # updated units
- gunits = self.defaults["gerber_exp_units"]
- gwhole = self.defaults["gerber_exp_integer"]
- gfract = self.defaults["gerber_exp_decimals"]
- gzeros = self.defaults["gerber_exp_zeros"]
- fc_units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
- if fc_units == 'MM':
- factor = 1 if gunits == 'MM' else 0.03937
- else:
- factor = 25.4 if gunits == 'MM' else 1
- def make_gerber():
- try:
- time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
- header = 'G04*\n'
- header += 'G04 RS-274X GERBER GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s*\n' % \
- (str(self.version), str(self.version_date))
- header += 'G04 Filename: %s*' % str(obj_name) + '\n'
- header += 'G04 Created on : %s*' % time_str + '\n'
- header += '%%FS%sAX%s%sY%s%s*%%\n' % (gzeros, gwhole, gfract, gwhole, gfract)
- header += "%MO{units}*%\n".format(units=gunits)
- for apid in obj.apertures:
- if obj.apertures[apid]['type'] == 'C':
- header += "%ADD{apid}{type},{size}*%\n".format(
- apid=str(apid),
- type='C',
- size=(factor * obj.apertures[apid]['size'])
- )
- elif obj.apertures[apid]['type'] == 'R':
- header += "%ADD{apid}{type},{width}X{height}*%\n".format(
- apid=str(apid),
- type='R',
- width=(factor * obj.apertures[apid]['width']),
- height=(factor * obj.apertures[apid]['height'])
- )
- elif obj.apertures[apid]['type'] == 'O':
- header += "%ADD{apid}{type},{width}X{height}*%\n".format(
- apid=str(apid),
- type='O',
- width=(factor * obj.apertures[apid]['width']),
- height=(factor * obj.apertures[apid]['height'])
- )
- header += '\n'
- # obsolete units but some software may need it
- if gunits == 'IN':
- header += 'G70*\n'
- else:
- header += 'G71*\n'
- # Absolute Mode
- header += 'G90*\n'
- header += 'G01*\n'
- # positive polarity
- header += '%LPD*%\n'
- footer = 'M02*\n'
- gerber_code = obj.export_gerber(gwhole, gfract, g_zeros=gzeros, factor=factor)
- exported_gerber = header
- exported_gerber += gerber_code
- exported_gerber += footer
- if local_use is None:
- try:
- with open(filename, 'w') as fp:
- fp.write(exported_gerber)
- except PermissionError:
- self.inform.emit('[WARNING] %s' %
- _("Permission denied, saving not possible.\n"
- "Most likely another app is holding the file open and not accessible."))
- return 'fail'
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("Gerber", filename)
- self.file_saved.emit("Gerber", filename)
- self.inform.emit('[success] %s: %s' %
- (_("Gerber file exported to"), filename))
- else:
- return exported_gerber
- except Exception as e:
- log.debug("App.export_gerber.make_gerber() --> %s" % str(e))
- return 'fail'
- if use_thread is True:
- with self.proc_container.new(_("Exporting Gerber")) as proc:
- def job_thread_grb(app_obj):
- ret = make_gerber()
- if ret == 'fail':
- self.inform.emit('[ERROR_NOTCL] %s' %
- _('Could not export Gerber file.'))
- return
- self.worker_task.emit({'fcn': job_thread_grb, 'params': [self]})
- else:
- ret = make_gerber()
- if ret == 'fail':
- self.inform.emit('[ERROR_NOTCL] %s' %
- _('Could not export Gerber file.'))
- return 'fail'
- if local_use is not None:
- return ret
- def export_dxf(self, obj_name, filename, use_thread=True):
- """
- Exports a Geometry Object to an DXF file.
- :param obj_name: the name of the FlatCAM object to be saved as DXF
- :param filename: Path to the DXF file to save to.
- :param use_thread: if to be run in a separate thread
- :return:
- """
- self.report_usage("export_dxf()")
- if filename is None:
- filename = self.defaults["global_last_save_folder"]
- self.log.debug("export_dxf()")
- format_exc = ''
- units = ''
- try:
- obj = self.collection.get_by_name(str(obj_name))
- except:
- # TODO: The return behavior has not been established... should raise exception?
- return "Could not retrieve object: %s" % obj_name
- # updated units
- units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
- if units == 'IN' or units == 'INCH':
- units = 'INCH'
- elif units == 'MM' or units == 'METIRC':
- units ='METRIC'
- def make_dxf():
- try:
- dxf_code = obj.export_dxf()
- dxf_code.saveas(filename)
- if self.defaults["global_open_style"] is False:
- self.file_opened.emit("DXF", filename)
- self.file_saved.emit("DXF", filename)
- self.inform.emit('[success] %s: %s' %
- (_("DXF file exported to"), filename))
- except:
- return 'fail'
- if use_thread is True:
- with self.proc_container.new(_("Exporting DXF")) as proc:
- def job_thread_exc(app_obj):
- ret = make_dxf()
- if ret == 'fail':
- app_obj.inform.emit('[WARNING_NOTCL] %s' %
- _('Could not export DXF file.'))
- return
- self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]})
- else:
- ret = make_dxf()
- if ret == 'fail':
- self.inform.emit('[WARNING_NOTCL] %s' %
- _('Could not export DXF file.'))
- return
- def import_svg(self, filename, geo_type='geometry', outname=None):
- """
- Adds a new Geometry Object to the projects and populates
- it with shapes extracted from the SVG file.
- :param filename: Path to the SVG file.
- :param geo_type: Type of FlatCAM object that will be created from SVG
- :param outname:
- :return:
- """
- self.report_usage("import_svg()")
- obj_type = ""
- if geo_type is None or geo_type == "geometry":
- obj_type = "geometry"
- elif geo_type == "gerber":
- obj_type = geo_type
- else:
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Not supported type is picked as parameter. "
- "Only Geometry and Gerber are supported"))
- return
- units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
- def obj_init(geo_obj, app_obj):
- geo_obj.import_svg(filename, obj_type, units=units)
- geo_obj.multigeo = False
- with self.proc_container.new(_("Importing SVG")) as proc:
- # Object name
- name = outname or filename.split('/')[-1].split('\\')[-1]
- self.new_object(obj_type, name, obj_init, autoselected=False)
- self.progress.emit(20)
- # Register recent file
- self.file_opened.emit("svg", filename)
- # GUI feedback
- self.inform.emit('[success] %s: %s' %
- (_("Opened"), filename))
- self.progress.emit(100)
- def import_dxf(self, filename, geo_type='geometry', outname=None):
- """
- Adds a new Geometry Object to the projects and populates
- it with shapes extracted from the DXF file.
- :param filename: Path to the DXF file.
- :param geo_type: Type of FlatCAM object that will be created from DXF
- :param outname:
- :type putname: str
- :return:
- """
- self.report_usage("import_dxf()")
- obj_type = ""
- if geo_type is None or geo_type == "geometry":
- obj_type = "geometry"
- elif geo_type == "gerber":
- obj_type = geo_type
- else:
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Not supported type is picked as parameter. Only Geometry and Gerber are supported"))
- return
- units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
- def obj_init(geo_obj, app_obj):
- geo_obj.import_dxf(filename, obj_type, units=units)
- geo_obj.multigeo = False
- with self.proc_container.new(_("Importing DXF")) as proc:
- # Object name
- name = outname or filename.split('/')[-1].split('\\')[-1]
- self.new_object(obj_type, name, obj_init, autoselected=False)
- self.progress.emit(20)
- # Register recent file
- self.file_opened.emit("dxf", filename)
- # GUI feedback
- self.inform.emit('[success] %s: %s' % (_("Opened"), filename))
- self.progress.emit(100)
- def import_image(self, filename, o_type='gerber', dpi=96, mode='black', mask=[250, 250, 250, 250], outname=None):
- """
- Adds a new Geometry Object to the projects and populates
- it with shapes extracted from the SVG file.
- :param filename: Path to the SVG file.
- :param o_type: type of FlatCAM objeect
- :param dpi: dot per inch
- :param mode: black or color
- :param mask: dictate the level of detail
- :param outname: name for the resulting file
- :return:
- """
- self.report_usage("import_image()")
- if o_type is None or o_type == "geometry":
- obj_type = "geometry"
- elif o_type == "gerber":
- obj_type = o_type
- else:
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Not supported type is picked as parameter. "
- "Only Geometry and Gerber are supported"))
- return
- def obj_init(geo_obj, app_obj):
- geo_obj.import_image(filename, units=units, dpi=dpi, mode=mode, mask=mask)
- geo_obj.multigeo = False
- with self.proc_container.new(_("Importing Image")) as proc:
- # Object name
- name = outname or filename.split('/')[-1].split('\\')[-1]
- units = self.ui.general_defaults_form.general_app_group.units_radio.get_value()
- self.new_object(obj_type, name, obj_init)
- self.progress.emit(20)
- # Register recent file
- self.file_opened.emit("image", filename)
- # GUI feedback
- self.inform.emit('[success] %s: %s' %
- (_("Opened"), filename))
- self.progress.emit(100)
- def open_gerber(self, filename, outname=None):
- """
- Opens a Gerber file, parses it and creates a new object for
- it in the program. Thread-safe.
- :param outname: Name of the resulting object. None causes the
- name to be that of the file.
- :param filename: Gerber file filename
- :type filename: str
- :param follow: If true, the parser will not create polygons, just lines
- following the gerber path.
- :type follow: bool
- :return: None
- """
- # How the object should be initialized
- def obj_init(gerber_obj, app_obj):
- assert isinstance(gerber_obj, FlatCAMGerber), \
- "Expected to initialize a FlatCAMGerber but got %s" % type(gerber_obj)
- # Opening the file happens here
- self.progress.emit(30)
- try:
- gerber_obj.parse_file(filename)
- except IOError:
- app_obj.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Failed to open file"), filename))
- app_obj.progress.emit(0)
- return "fail"
- except ParseError as err:
- app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' %
- (_("Failed to parse file"), filename, str(err)))
- app_obj.progress.emit(0)
- self.log.error(str(err))
- return "fail"
- except Exception as e:
- log.debug("App.open_gerber() --> %s" % str(e))
- msg = '[ERROR] %s' % \
- _("An internal error has occurred. See shell.\n")
- msg += traceback.format_exc()
- app_obj.inform.emit(msg)
- return "fail"
- if gerber_obj.is_empty():
- # app_obj.inform.emit("[ERROR] No geometry found in file: " + filename)
- # self.collection.set_active(gerber_obj.options["name"])
- # self.collection.delete_active()
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Object is not Gerber file or empty. Aborting object creation."))
- return "fail"
- # Further parsing
- self.progress.emit(70) # TODO: Note the mixture of self and app_obj used here
- App.log.debug("open_gerber()")
- with self.proc_container.new(_("Opening Gerber")) as proc:
- self.progress.emit(10)
- # Object name
- name = outname or filename.split('/')[-1].split('\\')[-1]
- # # ## Object creation # ##
- ret = self.new_object("gerber", name, obj_init, autoselected=False)
- if ret == 'fail':
- self.inform.emit('[ERROR_NOTCL]%s' %
- _(' Open Gerber failed. Probable not a Gerber file.'))
- return
- # Register recent file
- self.file_opened.emit("gerber", filename)
- self.progress.emit(100)
- # GUI feedback
- self.inform.emit('[success] %s: %s' %
- (_("Opened"), filename))
- def open_excellon(self, filename, outname=None, plot=True):
- """
- Opens an Excellon file, parses it and creates a new object for
- it in the program. Thread-safe.
- :param outname: Name of the resulting object. None causes the
- name to be that of the file.
- :param filename: Excellon file filename
- :type filename: str
- :return: None
- """
- App.log.debug("open_excellon()")
- # How the object should be initialized
- def obj_init(excellon_obj, app_obj):
- # self.progress.emit(20)
- try:
- ret = excellon_obj.parse_file(filename=filename)
- if ret == "fail":
- log.debug("Excellon parsing failed.")
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("This is not Excellon file."))
- return "fail"
- except IOError:
- app_obj.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Cannot open file"), filename))
- log.debug("Could not open Excellon object.")
- self.progress.emit(0) # TODO: self and app_bjj mixed
- return "fail"
- except:
- msg = '[ERROR_NOTCL] %s' % \
- _("An internal error has occurred. See shell.\n")
- msg += traceback.format_exc()
- app_obj.inform.emit(msg)
- return "fail"
- ret = excellon_obj.create_geometry()
- if ret == 'fail':
- log.debug("Could not create geometry for Excellon object.")
- return "fail"
- for tool in excellon_obj.tools:
- if excellon_obj.tools[tool]['solid_geometry']:
- return
- app_obj.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("No geometry found in file"), filename))
- return "fail"
- with self.proc_container.new(_("Opening Excellon.")):
- # Object name
- name = outname or filename.split('/')[-1].split('\\')[-1]
- ret_val = self.new_object("excellon", name, obj_init, autoselected=False, plot=plot)
- if ret_val == 'fail':
- self.inform.emit('[ERROR_NOTCL] %s' %
- _('Open Excellon file failed. Probable not an Excellon file.'))
- return
- # Register recent file
- self.file_opened.emit("excellon", filename)
- # GUI feedback
- self.inform.emit('[success] %s: %s' %
- (_("Opened"), filename))
- def open_gcode(self, filename, outname=None, plot=True):
- """
- Opens a G-gcode file, parses it and creates a new object for
- it in the program. Thread-safe.
- :param outname: Name of the resulting object. None causes the name to be that of the file.
- :param filename: G-code file filename
- :type filename: str
- :return: None
- """
- App.log.debug("open_gcode()")
- # How the object should be initialized
- def obj_init(job_obj, app_obj_):
- """
- :param job_obj: the resulting object
- :type app_obj_: App
- """
- assert isinstance(app_obj_, App), \
- "Initializer expected App, got %s" % type(app_obj_)
- try:
- f = open(filename)
- gcode = f.read()
- f.close()
- except IOError:
- app_obj_.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Failed to open"), filename))
- return "fail"
- job_obj.gcode = gcode
- ret = job_obj.gcode_parse()
- if ret == "fail":
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("This is not GCODE"))
- return "fail"
- job_obj.create_geometry()
- with self.proc_container.new(_("Opening G-Code.")):
- # Object name
- name = outname or filename.split('/')[-1].split('\\')[-1]
- # New object creation and file processing
- ret = self.new_object("cncjob", name, obj_init, autoselected=False, plot=plot)
- if ret == 'fail':
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to create CNCJob Object. Probable not a GCode file.\n "
- "Attempting to create a FlatCAM CNCJob Object from "
- "G-Code file failed during processing"))
- return "fail"
- # Register recent file
- self.file_opened.emit("cncjob", filename)
- # GUI feedback
- self.inform.emit('[success] %s: %s' %
- (_("Opened"), filename))
- def open_script(self, filename, outname=None, silent=False):
- """
- Opens a Script file, parses it and creates a new object for
- it in the program. Thread-safe.
- :param outname: Name of the resulting object. None causes the name to be that of the file.
- :param filename: Script file filename
- :type filename: str
- :return: None
- """
- App.log.debug("open_script()")
- with self.proc_container.new(_("Opening TCL Script...")):
- try:
- with open(filename, "r") as opened_script:
- script_content = opened_script.readlines()
- script_content = ''.join(script_content)
- if silent is False:
- self.inform.emit('[success] %s' % _("TCL script file opened in Code Editor."))
- except Exception as e:
- log.debug("App.open_script() -> %s" % str(e))
- self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to open TCL Script."))
- return
- # Object name
- script_name = outname or filename.split('/')[-1].split('\\')[-1]
- # New object creation and file processing
- self.on_filenewscript(name=script_name, text=script_content)
- # Register recent file
- self.file_opened.emit("script", filename)
- # GUI feedback
- self.inform.emit('[success] %s: %s' % (_("Opened"), filename))
- def open_config_file(self, filename, run_from_arg=None):
- """
- Loads a config file from the specified file.
- :param filename: Name of the file from which to load.
- :type filename: str
- :return: None
- """
- App.log.debug("Opening config file: " + filename)
- if run_from_arg:
- self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
- "Canvas initialization finished in"), '%.2f' % self.used_time,
- _("Opening FlatCAM Config file.")),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- # add the tab if it was closed
- self.ui.plot_tab_area.addTab(self.ui.text_editor_tab, _("Code Editor"))
- # first clear previous text in text editor (if any)
- self.ui.text_editor_tab.code_editor.clear()
- # Switch plot_area to CNCJob tab
- self.ui.plot_tab_area.setCurrentWidget(self.ui.text_editor_tab)
- try:
- if filename:
- f = QtCore.QFile(filename)
- if f.open(QtCore.QIODevice.ReadOnly):
- stream = QtCore.QTextStream(f)
- gcode_edited = stream.readAll()
- self.ui.text_editor_tab.code_editor.setPlainText(gcode_edited)
- f.close()
- except IOError:
- App.log.error("Failed to open config file: %s" % filename)
- self.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Failed to open config file"), filename))
- return
- def open_project(self, filename, run_from_arg=None, plot=True, cli=None):
- """
- Loads a project from the specified file.
- 1) Loads and parses file
- 2) Registers the file as recently opened.
- 3) Calls on_file_new()
- 4) Updates options
- 5) Calls new_object() with the object's from_dict() as init method.
- 6) Calls plot_all() if plot=True
- :param filename: Name of the file from which to load.
- :type filename: str
- :param run_from_arg: True if run for arguments
- :param plot: If True plot all objects in the project
- :param cli: run from command line
- :return: None
- """
- App.log.debug("Opening project: " + filename)
- # for some reason, setting ui_title does not work when this method is called from Tcl Shell
- # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled)
- if cli is None:
- self.set_ui_title(name=_("Loading Project ... Please Wait ..."))
- if run_from_arg:
- self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
- "Canvas initialization finished in"), '%.2f' % self.used_time,
- _("Opening FlatCAM Project file.")),
- alignment=Qt.AlignBottom | Qt.AlignLeft,
- color=QtGui.QColor("gray"))
- # Open and parse an uncompressed Project file
- try:
- f = open(filename, 'r')
- except IOError:
- App.log.error("Failed to open project file: %s" % filename)
- self.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Failed to open project file"), filename))
- return
- try:
- d = json.load(f, object_hook=dict2obj)
- except Exception as e:
- App.log.error("Failed to parse project file, trying to see if it loads as an LZMA archive: %s because %s" %
- (filename, str(e)))
- f.close()
- # Open and parse a compressed Project file
- try:
- with lzma.open(filename) as f:
- file_content = f.read().decode('utf-8')
- d = json.loads(file_content, object_hook=dict2obj)
- except Exception as e:
- App.log.error("Failed to open project file: %s with error: %s" % (filename, str(e)))
- self.inform.emit('[ERROR_NOTCL] %s: %s' %
- (_("Failed to open project file"), filename))
- return
- # Clear the current project
- # # NOT THREAD SAFE # ##
- if run_from_arg is True:
- pass
- elif cli is True:
- self.delete_selection_shape()
- else:
- self.on_file_new()
- # Project options
- self.options.update(d['options'])
- self.project_filename = filename
- # for some reason, setting ui_title does not work when this method is called from Tcl Shell
- # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled)
- if cli is None:
- self.set_screen_units(self.options["units"])
- # Re create objects
- App.log.debug(" **************** Started PROEJCT loading... **************** ")
- for obj in d['objs']:
- def obj_init(obj_inst, app_inst):
- obj_inst.from_dict(obj)
- App.log.debug("Recreating from opened project an %s object: %s" %
- (obj['kind'].capitalize(), obj['options']['name']))
- # for some reason, setting ui_title does not work when this method is called from Tcl Shell
- # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled)
- if cli is None:
- self.set_ui_title(name="{} {}: {}".format(_("Loading Project ... restoring"),
- obj['kind'].upper(),
- obj['options']['name']
- )
- )
- self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=plot)
- self.inform.emit('[success] %s: %s' %
- (_("Project loaded from"), filename))
- self.should_we_save = False
- self.file_opened.emit("project", filename)
- # for some reason, setting ui_title does not work when this method is called from Tcl Shell
- # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled)
- if cli is None:
- self.set_ui_title(name=self.project_filename)
- App.log.debug(" **************** Finished PROJECT loading... **************** ")
- def propagate_defaults(self, silent=False):
- """
- This method is used to set default values in classes. It's
- an alternative to project options but allows the use
- of values invisible to the user.
- :return: None
- """
- if silent is False:
- self.log.debug("propagate_defaults()")
- # Which objects to update the given parameters.
- routes = {
- "global_zdownrate": CNCjob,
- "excellon_zeros": Excellon,
- "excellon_format_upper_in": Excellon,
- "excellon_format_lower_in": Excellon,
- "excellon_format_upper_mm": Excellon,
- "excellon_format_lower_mm": Excellon,
- "excellon_units": Excellon,
- "gerber_use_buffer_for_union": Gerber,
- "geometry_multidepth": Geometry
- }
- for param in routes:
- if param in routes[param].defaults:
- try:
- routes[param].defaults[param] = self.defaults[param]
- if silent is False:
- self.log.debug(" " + param + " OK")
- except KeyError:
- if silent is False:
- self.log.debug(" ERROR: " + param + " not in defaults.")
- else:
- # Try extracting the name:
- # classname_param here is param in the object
- if param.find(routes[param].__name__.lower() + "_") == 0:
- p = param[len(routes[param].__name__) + 1:]
- if p in routes[param].defaults:
- routes[param].defaults[p] = self.defaults[param]
- if silent is False:
- self.log.debug(" " + param + " OK!")
- def plot_all(self, zoom=True):
- """
- Re-generates all plots from all objects.
- :return: None
- """
- self.log.debug("Plot_all()")
- self.inform.emit('[success] %s...' % _("Redrawing all objects"))
- for obj in self.collection.get_list():
- def worker_task(obj):
- with self.proc_container.new("Plotting"):
- obj.plot(kind=self.defaults["cncjob_plot_kind"])
- if zoom:
- self.object_plotted.emit(obj)
- # Send to worker
- self.worker_task.emit({'fcn': worker_task, 'params': [obj]})
- def register_folder(self, filename):
- self.defaults["global_last_folder"] = os.path.split(str(filename))[0]
- def register_save_folder(self, filename):
- self.defaults["global_last_save_folder"] = os.path.split(str(filename))[0]
- def set_progress_bar(self, percentage, text=""):
- self.ui.progress_bar.setValue(int(percentage))
- def setup_shell(self):
- """
- Creates shell functions. Runs once at startup.
- :return: None
- """
- self.log.debug("setup_shell()")
- def shelp(p=None):
- if not p:
- return _("Available commands:\n") + \
- '\n'.join([' ' + cmd for cmd in sorted(commands)]) + \
- _("\n\nType help <command_name> for usage.\n Example: help open_gerber")
- if p not in commands:
- return "Unknown command: %s" % p
- return commands[p]["help"]
- # --- Migrated to new architecture ---
- # def options(name):
- # ops = self.collection.get_by_name(str(name)).options
- # return '\n'.join(["%s: %s" % (o, ops[o]) for o in ops])
- def h(*args):
- """
- Pre-processes arguments to detect '-keyword value' pairs into dictionary
- and standalone parameters into list.
- """
- kwa = {}
- a = []
- n = len(args)
- name = None
- for i in range(n):
- match = re.search(r'^-([a-zA-Z].*)', args[i])
- if match:
- assert name is None
- name = match.group(1)
- continue
- if name is None:
- a.append(args[i])
- else:
- kwa[name] = args[i]
- name = None
- return a, kwa
- @contextmanager
- def wait_signal(signal, timeout=10000):
- """
- Block loop until signal emitted, timeout (ms) elapses
- or unhandled exception happens in a thread.
- :param timeout: time after which the loop is exited
- :param signal: Signal to wait for.
- """
- loop = QtCore.QEventLoop()
- # Normal termination
- signal.connect(loop.quit)
- # Termination by exception in thread
- self.thread_exception.connect(loop.quit)
- status = {'timed_out': False}
- def report_quit():
- status['timed_out'] = True
- loop.quit()
- yield
- # Temporarily change how exceptions are managed.
- oeh = sys.excepthook
- ex = []
- def except_hook(type_, value, traceback_):
- ex.append(value)
- oeh(type_, value, traceback_)
- sys.excepthook = except_hook
- # Terminate on timeout
- if timeout is not None:
- QtCore.QTimer.singleShot(timeout, report_quit)
- # # ## Block ## ##
- loop.exec_()
- # Restore exception management
- sys.excepthook = oeh
- if ex:
- self.raiseTclError(str(ex[0]))
- if status['timed_out']:
- raise Exception('Timed out!')
- def make_docs():
- output = ''
- import collections
- od = collections.OrderedDict(sorted(commands.items()))
- for cmd_, val in od.items():
- output += cmd_ + ' \n' + ''.join(['~'] * len(cmd_)) + '\n'
- t = val['help']
- usage_i = t.find('>')
- if usage_i < 0:
- expl = t
- output += expl + '\n\n'
- continue
- expl = t[:usage_i - 1]
- output += expl + '\n\n'
- end_usage_i = t[usage_i:].find('\n')
- if end_usage_i < 0:
- end_usage_i = len(t[usage_i:])
- output += ' ' + t[usage_i:] + '\n No parameters.\n'
- else:
- extras = t[usage_i+end_usage_i+1:]
- parts = [s.strip() for s in extras.split('\n')]
- output += ' ' + t[usage_i:usage_i+end_usage_i] + '\n'
- for p in parts:
- output += ' ' + p + '\n\n'
- return output
- '''
- Howto implement TCL shell commands:
- All parameters passed to command should be possible to set as None and test it afterwards.
- This is because we need to see error caused in tcl,
- if None value as default parameter is not allowed TCL will return empty error.
- Use:
- def mycommand(name=None,...):
- Test it like this:
- if name is None:
- self.raise_tcl_error('Argument name is missing.')
- When error ocurre, always use raise_tcl_error, never return "sometext" on error,
- otherwise we will miss it and processing will silently continue.
- Method raise_tcl_error pass error into TCL interpreter, then raise python exception,
- which is catched in exec_command and displayed in TCL shell console with red background.
- Error in console is displayed with TCL trace.
- This behavior works only within main thread,
- errors with promissed tasks can be catched and detected only with log.
- TODO: this problem have to be addressed somehow, maybe rewrite promissing to be blocking somehow for
- TCL shell.
- Kamil's comment: I will rewrite existing TCL commands from time to time to follow this rules.
- '''
- commands = {
- 'help': {
- 'fcn': shelp,
- 'help': _("Shows list of commands.")
- },
- }
- # Import/overwrite tcl commands as objects of TclCommand descendants
- # This modifies the variable 'commands'.
- tclCommands.register_all_commands(self, commands)
- # Add commands to the tcl interpreter
- for cmd in commands:
- self.tcl.createcommand(cmd, commands[cmd]['fcn'])
- # Make the tcl puts function return instead of print to stdout
- self.tcl.eval('''
- rename puts original_puts
- proc puts {args} {
- if {[llength $args] == 1} {
- return "[lindex $args 0]"
- } else {
- eval original_puts $args
- }
- }
- ''')
- def setup_recent_items(self):
- # TODO: Move this to constructor
- icons = {
- "gerber": "share/flatcam_icon16.png",
- "excellon": "share/drill16.png",
- 'geometry': "share/geometry16.png",
- "cncjob": "share/cnc16.png",
- "script": "share/script_new24.png",
- "document": "share/notes16_1.png",
- "project": "share/project16.png",
- "svg": "share/geometry16.png",
- "dxf": "share/dxf16.png",
- "pdf": "share/pdf32.png",
- "image": "share/image16.png"
- }
- openers = {
- 'gerber': lambda fname: self.worker_task.emit({'fcn': self.open_gerber, 'params': [fname]}),
- 'excellon': lambda fname: self.worker_task.emit({'fcn': self.open_excellon, 'params': [fname]}),
- 'geometry': lambda fname: self.worker_task.emit({'fcn': self.import_dxf, 'params': [fname]}),
- 'cncjob': lambda fname: self.worker_task.emit({'fcn': self.open_gcode, 'params': [fname]}),
- "script": lambda fname: self.worker_task.emit({'fcn': self.open_script, 'params': [fname]}),
- "document": None,
- 'project': self.open_project,
- 'svg': self.import_svg,
- 'dxf': self.import_dxf,
- 'image': self.import_image,
- 'pdf': lambda fname: self.worker_task.emit({'fcn': self.pdf_tool.open_pdf, 'params': [fname]})
- }
- # Open recent file for files
- try:
- f = open(self.data_path + '/recent.json')
- except IOError:
- App.log.error("Failed to load recent item list.")
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to load recent item list."))
- return
- try:
- self.recent = json.load(f)
- except json.scanner.JSONDecodeError:
- App.log.error("Failed to parse recent item list.")
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to parse recent item list."))
- f.close()
- return
- f.close()
- # Open recent file for projects
- try:
- fp = open(self.data_path + '/recent_projects.json')
- except IOError:
- App.log.error("Failed to load recent project item list.")
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to load recent projects item list."))
- return
- try:
- self.recent_projects = json.load(fp)
- except json.scanner.JSONDecodeError:
- App.log.error("Failed to parse recent project item list.")
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Failed to parse recent project item list."))
- fp.close()
- return
- fp.close()
- # Closure needed to create callbacks in a loop.
- # Otherwise late binding occurs.
- def make_callback(func, fname):
- def opener():
- func(fname)
- return opener
- def reset_recent_files():
- # Reset menu
- self.ui.recent.clear()
- self.recent = []
- try:
- f = open(self.data_path + '/recent.json', 'w')
- except IOError:
- App.log.error("Failed to open recent items file for writing.")
- return
- json.dump(self.recent, f)
- def reset_recent_projects():
- # Reset menu
- self.ui.recent_projects.clear()
- self.recent_projects = []
- try:
- fp = open(self.data_path + '/recent_projects.json', 'w')
- except IOError:
- App.log.error("Failed to open recent projects items file for writing.")
- return
- json.dump(self.recent, fp)
- # Reset menu
- self.ui.recent.clear()
- self.ui.recent_projects.clear()
- # Create menu items for projects
- for recent in self.recent_projects:
- filename = recent['filename'].split('/')[-1].split('\\')[-1]
- if recent['kind'] == 'project':
- try:
- action = QtWidgets.QAction(QtGui.QIcon(icons[recent["kind"]]), filename, self)
- # Attach callback
- o = make_callback(openers[recent["kind"]], recent['filename'])
- action.triggered.connect(o)
- self.ui.recent_projects.addAction(action)
- except KeyError:
- App.log.error("Unsupported file type: %s" % recent["kind"])
- # Last action in Recent Files menu is one that Clear the content
- clear_action_proj = QtWidgets.QAction(QtGui.QIcon('share/trash32.png'), (_("Clear Recent files")), self)
- clear_action_proj.triggered.connect(reset_recent_projects)
- self.ui.recent_projects.addSeparator()
- self.ui.recent_projects.addAction(clear_action_proj)
- # Create menu items for files
- for recent in self.recent:
- filename = recent['filename'].split('/')[-1].split('\\')[-1]
- if recent['kind'] != 'project':
- try:
- action = QtWidgets.QAction(QtGui.QIcon(icons[recent["kind"]]), filename, self)
- # Attach callback
- o = make_callback(openers[recent["kind"]], recent['filename'])
- action.triggered.connect(o)
- self.ui.recent.addAction(action)
- except KeyError:
- App.log.error("Unsupported file type: %s" % recent["kind"])
- # Last action in Recent Files menu is one that Clear the content
- clear_action = QtWidgets.QAction(QtGui.QIcon('share/trash32.png'), (_("Clear Recent files")), self)
- clear_action.triggered.connect(reset_recent_files)
- self.ui.recent.addSeparator()
- self.ui.recent.addAction(clear_action)
- # self.builder.get_object('open_recent').set_submenu(recent_menu)
- # self.ui.menufilerecent.set_submenu(recent_menu)
- # recent_menu.show_all()
- # self.ui.recent.show()
- self.log.debug("Recent items list has been populated.")
- def setup_component_editor(self):
- # label = QtWidgets.QLabel("Choose an item from Project")
- # label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
- sel_title = QtWidgets.QTextEdit(
- _('<b>Shortcut Key List</b>'))
- sel_title.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
- sel_title.setFrameStyle(QtWidgets.QFrame.NoFrame)
- settings = QSettings("Open Source", "FlatCAM")
- if settings.contains("notebook_font_size"):
- fsize = settings.value('notebook_font_size', type=int)
- else:
- fsize = 12
- tsize = fsize + int(fsize / 2)
- # selected_text = (_('''
- # <p><span style="font-size:{tsize}px"><strong>Selected Tab - Choose an Item from Project Tab</strong></span></p>
- #
- # <p><span style="font-size:{fsize}px"><strong>Details</strong>:<br />
- # The normal flow when working in FlatCAM is the following:</span></p>
- #
- # <ol>
- # <li><span style="font-size:{fsize}px">Loat/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG file into
- # FlatCAM using either the menu's, toolbars, key shortcuts or
- # even dragging and dropping the files on the GUI.<br />
- # <br />
- # You can also load a <strong>FlatCAM project</strong> by double clicking on the project file, drag & drop of the
- # file into the FLATCAM GUI or through the menu/toolbar links offered within the app.</span><br />
- # </li>
- # <li><span style="font-size:{fsize}px">Once an object is available in the Project Tab, by selecting it and then
- # focusing on <strong>SELECTED TAB </strong>(more simpler is to double click the object name in the
- # Project Tab), <strong>SELECTED TAB </strong>will be updated with the object properties according to
- # it's kind: Gerber, Excellon, Geometry or CNCJob object.<br />
- # <br />
- # If the selection of the object is done on the canvas by single click instead, and the <strong>SELECTED TAB</strong>
- # is in focus, again the object properties will be displayed into the Selected Tab. Alternatively,
- # double clicking on the object on the canvas will bring the <strong>SELECTED TAB</strong> and populate
- # it even if it was out of focus.<br />
- # <br />
- # You can change the parameters in this screen and the flow direction is like this:<br />
- # <br />
- # <strong>Gerber/Excellon Object</strong> -> Change Param -> Generate Geometry -><strong> Geometry Object
- # </strong>-> Add tools (change param in Selected Tab) -> Generate CNCJob -><strong> CNCJob Object
- # </strong>-> Verify GCode (through Edit CNC Code) and/or append/prepend to GCode (again, done in
- # <strong>SELECTED TAB) </strong>-> Save GCode</span></li>
- # </ol>
- #
- # <p><span style="font-size:{fsize}px">A list of key shortcuts is available through an menu entry in
- # <strong>Help -> Shortcuts List</strong> or through it's own key shortcut:
- # <strong>F3</strong>.</span></p>
- #
- # ''').format(fsize=fsize, tsize=tsize))
- selected_text = '''
- <p><span style="font-size:{tsize}px"><strong>{title}</strong></span></p>
- <p><span style="font-size:{fsize}px"><strong>{subtitle}</strong>:<br />
- {s1}</span></p>
- <ol>
- <li><span style="font-size:{fsize}px">{s2}<br />
- <br />
- {s3}</span><br />
- </li>
- <li><span style="font-size:{fsize}px">{s4}<br />
- </li>
- <br />
- <li><span style="font-size:{fsize}px">{s5}<br />
- </li>
- <br />
- <li><span style="font-size:{fsize}px">{s6}<br />
- <br />
- {s7}</span></li>
- </ol>
- <p><span style="font-size:{fsize}px">{s8}</span></p>
- '''.format(
- title=_("Selected Tab - Choose an Item from Project Tab"),
- subtitle=_("Details"),
- s1=_("The normal flow when working in FlatCAM is the following:"),
- s2=_("Load/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG file into FlatCAM "
- "using either the toolbars, key shortcuts or even dragging and dropping the "
- "files on the GUI."),
- s3=_("You can also load a FlatCAM project by double clicking on the project file, "
- "drag and drop of the file into the FLATCAM GUI or through the menu (or toolbar) "
- "actions offered within the app."),
- s4=_("Once an object is available in the Project Tab, by selecting it and then focusing "
- "on SELECTED TAB (more simpler is to double click the object name in the Project Tab, "
- "SELECTED TAB will be updated with the object properties according to its kind: "
- "Gerber, Excellon, Geometry or CNCJob object."),
- s5=_("If the selection of the object is done on the canvas by single click instead, "
- "and the SELECTED TAB is in focus, again the object properties will be displayed into the "
- "Selected Tab. Alternatively, double clicking on the object on the canvas will bring "
- "the SELECTED TAB and populate it even if it was out of focus."),
- s6=_("You can change the parameters in this screen and the flow direction is like this:"),
- s7=_("Gerber/Excellon Object --> Change Parameter --> Generate Geometry --> Geometry Object --> "
- "Add tools (change param in Selected Tab) --> Generate CNCJob --> CNCJob Object --> "
- "Verify GCode (through Edit CNC Code) and/or append/prepend to GCode "
- "(again, done in SELECTED TAB) --> Save GCode."),
- s8=_("A list of key shortcuts is available through an menu entry in Help --> Shortcuts List "
- "or through its own key shortcut: <b>F3</b>."),
- tsize=tsize,
- fsize=fsize
- )
- sel_title.setText(selected_text)
- sel_title.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
- self.ui.selected_scroll_area.setWidget(sel_title)
- def setup_obj_classes(self):
- """
- Sets up application specifics on the FlatCAMObj class.
- :return: None
- """
- FlatCAMObj.app = self
- ObjectCollection.app = self
- Gerber.app = self
- Excellon.app = self
- Geometry.app = self
- CNCjob.app = self
- FCProcess.app = self
- FCProcessContainer.app = self
- def version_check(self):
- """
- Checks for the latest version of the program. Alerts the
- user if theirs is outdated. This method is meant to be run
- in a separate thread.
- :return: None
- """
- self.log.debug("version_check()")
- if self.ui.general_defaults_form.general_app_group.send_stats_cb.get_value() is True:
- full_url = App.version_url + \
- "?s=" + str(self.defaults['global_serial']) + \
- "&v=" + str(self.version) + \
- "&os=" + str(self.os) + \
- "&" + urllib.parse.urlencode(self.defaults["global_stats"])
- else:
- # no_stats dict; just so it won't break things on website
- no_ststs_dict = {}
- no_ststs_dict["global_ststs"] = {}
- full_url = App.version_url + \
- "?s=" + str(self.defaults['global_serial']) + \
- "&v=" + str(self.version) + \
- "&os=" + str(self.os) + \
- "&" + urllib.parse.urlencode(no_ststs_dict["global_ststs"])
- App.log.debug("Checking for updates @ %s" % full_url)
- # ## Get the data
- try:
- f = urllib.request.urlopen(full_url)
- except:
- # App.log.warning("Failed checking for latest version. Could not connect.")
- self.log.warning("Failed checking for latest version. Could not connect.")
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("Failed checking for latest version. Could not connect."))
- return
- try:
- data = json.load(f)
- except Exception as e:
- App.log.error("Could not parse information about latest version.")
- self.inform.emit('[ERROR_NOTCL] %s' %
- _("Could not parse information about latest version."))
- App.log.debug("json.load(): %s" % str(e))
- f.close()
- return
- f.close()
- # ## Latest version?
- if self.version >= data["version"]:
- App.log.debug("FlatCAM is up to date!")
- self.inform.emit('[success] %s' %
- _("FlatCAM is up to date!"))
- return
- App.log.debug("Newer version available.")
- self.message.emit(
- _("Newer Version Available"),
- _("There is a newer version of FlatCAM available for download:\n\n") +
- "<b>%s</b>" % str(data["name"]) + "\n%s" % str(data["message"]),
- _("info")
- )
- def on_plotcanvas_setup(self, container=None):
- """
- This is doing the setup for the plot area (VisPy canvas)
- :param container: widget where to install the canvas
- :return: None
- """
- if container:
- plot_container = container
- else:
- plot_container = self.ui.right_layout
- if self.is_legacy is False:
- self.plotcanvas = PlotCanvas(plot_container, self)
- else:
- self.plotcanvas = PlotCanvasLegacy(plot_container, self)
- # So it can receive key presses
- self.plotcanvas.native.setFocus()
- self.mm = self.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move_over_plot)
- self.mp = self.plotcanvas.graph_event_connect('mouse_press', self.on_mouse_click_over_plot)
- self.mr = self.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release_over_plot)
- self.mdc = self.plotcanvas.graph_event_connect('mouse_double_click', self.on_double_click_over_plot)
- # Keys over plot enabled
- self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent)
- if self.defaults['global_cursor_type'] == 'small':
- self.app_cursor = self.plotcanvas.new_cursor()
- else:
- self.app_cursor = self.plotcanvas.new_cursor(big=True)
- if self.ui.grid_snap_btn.isChecked():
- self.app_cursor.enabled = True
- else:
- self.app_cursor.enabled = False
- if self.is_legacy is False:
- self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1)
- else:
- # will use the default Matplotlib axes
- self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover')
- def on_zoom_fit(self, event):
- """
- Callback for zoom-out request. This can be either from the corresponding
- toolbar button or the '1' key when the canvas is focused. Calls ``self.adjust_axes()``
- with axes limits from the geometry bounds of all objects.
- :param event: Ignored.
- :return: None
- """
- if self.is_legacy is False:
- self.plotcanvas.fit_view()
- else:
- xmin, ymin, xmax, ymax = self.collection.get_bounds()
- width = xmax - xmin
- height = ymax - ymin
- xmin -= 0.05 * width
- xmax += 0.05 * width
- ymin -= 0.05 * height
- ymax += 0.05 * height
- self.plotcanvas.adjust_axes(xmin, ymin, xmax, ymax)
- def on_zoom_in(self):
- self.plotcanvas.zoom(1 / float(self.defaults['global_zoom_ratio']))
- def on_zoom_out(self):
- self.plotcanvas.zoom(float(self.defaults['global_zoom_ratio']))
- def disable_all_plots(self):
- self.report_usage("disable_all_plots()")
- self.disable_plots(self.collection.get_list())
- self.inform.emit('[success] %s' %
- _("All plots disabled."))
- def disable_other_plots(self):
- self.report_usage("disable_other_plots()")
- self.disable_plots(self.collection.get_non_selected())
- self.inform.emit('[success] %s' %
- _("All non selected plots disabled."))
- def enable_all_plots(self):
- self.report_usage("enable_all_plots()")
- self.enable_plots(self.collection.get_list())
- self.inform.emit('[success] %s' %
- _("All plots enabled."))
- def on_enable_sel_plots(self):
- log.debug("App.on_enable_sel_plot()")
- object_list = self.collection.get_selected()
- self.enable_plots(objects=object_list)
- self.inform.emit('[success] %s' %
- _("Selected plots enabled..."))
- def on_disable_sel_plots(self):
- log.debug("App.on_disable_sel_plot()")
- # self.inform.emit(_("Disabling plots ..."))
- object_list = self.collection.get_selected()
- self.disable_plots(objects=object_list)
- self.inform.emit('[success] %s' %
- _("Selected plots disabled..."))
- def enable_plots(self, objects):
- """
- Disables plots
- :param objects: list of Objects to be enabled
- :return:
- """
- log.debug("Enabling plots ...")
- # self.inform.emit(_("Working ..."))
- for obj in objects:
- if obj.options['plot'] is False:
- obj.options.set_change_callback(lambda x: None)
- obj.options['plot'] = True
- obj.options.set_change_callback(obj.on_options_change)
- def worker_task(objects):
- with self.proc_container.new(_("Enabling plots ...")):
- for obj in objects:
- # obj.options['plot'] = True
- if isinstance(obj, FlatCAMCNCjob):
- obj.plot(visible=True, kind=self.defaults["cncjob_plot_kind"])
- else:
- obj.plot(visible=True)
- self.worker_task.emit({'fcn': worker_task, 'params': [objects]})
- # self.plots_updated.emit()
- def disable_plots(self, objects):
- """
- Disables plots
- :param objects: list of Objects to be disabled
- :return:
- """
- # if no objects selected then do nothing
- if not self.collection.get_selected():
- return
- log.debug("Disabling plots ...")
- # self.inform.emit(_("Working ..."))
- for obj in objects:
- if obj.options['plot'] is True:
- obj.options.set_change_callback(lambda x: None)
- obj.options['plot'] = False
- obj.options.set_change_callback(obj.on_options_change)
- try:
- self.delete_selection_shape()
- except Exception as e:
- log.debug("App.disable_plots() --> %s" % str(e))
- # self.plots_updated.emit()
- def worker_task(objects):
- with self.proc_container.new(_("Disabling plots ...")):
- for obj in objects:
- # obj.options['plot'] = True
- if isinstance(obj, FlatCAMCNCjob):
- obj.plot(visible=False, kind=self.defaults["cncjob_plot_kind"])
- else:
- obj.plot(visible=False)
- self.worker_task.emit({'fcn': worker_task, 'params': [objects]})
- def toggle_plots(self, objects):
- """
- Toggle plots visibility
- :param objects: list of Objects for which to be toggled the visibility
- :return:
- """
- # if no objects selected then do nothing
- if not self.collection.get_selected():
- return
- log.debug("Toggling plots ...")
- self.inform.emit(_("Working ..."))
- for obj in objects:
- if obj.options['plot'] is False:
- obj.options['plot'] = True
- else:
- obj.options['plot'] = False
- self.plots_updated.emit()
- def clear_plots(self):
- objects = self.collection.get_list()
- for obj in objects:
- obj.clear(obj == objects[-1])
- # Clear pool to free memory
- self.clear_pool()
- def generate_cnc_job(self, objects):
- self.report_usage("generate_cnc_job()")
- # for obj in objects:
- # obj.generatecncjob()
- for obj in objects:
- obj.on_generatecnc_button_click()
- def save_project(self, filename, quit_action=False, silent=False):
- """
- Saves the current project to the specified file.
- :param filename: Name of the file in which to save.
- :type filename: str
- :param quit_action: if the project saving will be followed by an app quit; boolean
- :param silent: if True will not display status messages
- :return: None
- """
- self.log.debug("save_project()")
- self.save_in_progress = True
- with self.proc_container.new(_("Saving FlatCAM Project")):
- # Capture the latest changes
- # Current object
- try:
- self.collection.get_active().read_form()
- except:
- self.log.debug("There was no active object")
- pass
- # Project options
- self.options_read_form()
- # Serialize the whole project
- d = {"objs": [obj.to_dict() for obj in self.collection.get_list()],
- "options": self.options,
- "version": self.version}
- if self.defaults["global_save_compressed"] is True:
- with lzma.open(filename, "w", preset=int(self.defaults['global_compression_level'])) as f:
- g = json.dumps(d, default=to_dict, indent=2, sort_keys=True).encode('utf-8')
- # # Write
- f.write(g)
- self.inform.emit('[success] %s: %s' %
- (_("Project saved to"), filename))
- else:
- # Open file
- try:
- f = open(filename, 'w')
- except IOError:
- App.log.error("Failed to open file for saving: %s", filename)
- return
- # Write
- json.dump(d, f, default=to_dict, indent=2, sort_keys=True)
- f.close()
- # verification of the saved project
- # Open and parse
- try:
- saved_f = open(filename, 'r')
- except IOError:
- if silent is False:
- self.inform.emit('[ERROR_NOTCL] %s: %s %s' %
- (_("Failed to verify project file"), filename, _("Retry to save it."))
- )
- return
- try:
- saved_d = json.load(saved_f, object_hook=dict2obj)
- except:
- if silent is False:
- self.inform.emit('[ERROR_NOTCL] %s: %s %s' %
- (_("Failed to parse saved project file"), filename, _("Retry to save it."))
- )
- f.close()
- return
- saved_f.close()
- if silent is False:
- if 'version' in saved_d:
- self.inform.emit('[success] %s: %s' %
- (_("Project saved to"), filename))
- else:
- self.inform.emit('[ERROR_NOTCL] %s: %s %s' %
- (_("Failed to parse saved project file"), filename, _("Retry to save it."))
- )
- settings = QSettings("Open Source", "FlatCAM")
- lock_state = self.ui.lock_action.isChecked()
- settings.setValue('toolbar_lock', lock_state)
- # This will write the setting to the platform specific storage.
- del settings
- # if quit:
- # t = threading.Thread(target=lambda: self.check_project_file_size(1, filename=filename))
- # t.start()
- self.start_delayed_quit(delay=500, filename=filename, should_quit=quit_action)
- def start_delayed_quit(self, delay, filename, should_quit=None):
- """
- :param delay: period of checking if project file size is more than zero; in seconds
- :param filename: the name of the project file to be checked periodically for size more than zero
- :param should_quit: if the task finished will be followed by an app quit; boolean
- :return:
- """
- to_quit = should_quit
- self.save_timer = QtCore.QTimer()
- self.save_timer.setInterval(delay)
- self.save_timer.timeout.connect(lambda: self.check_project_file_size(filename=filename, should_quit=to_quit))
- self.save_timer.start()
- def check_project_file_size(self, filename, should_quit=None):
- """
- :param filename: the name of the project file to be checked periodically for size more than zero
- :param should_quit: will quit the app if True; boolean
- :return:
- """
- try:
- if os.stat(filename).st_size > 0:
- self.save_in_progress = False
- self.save_timer.stop()
- if should_quit:
- self.app_quit.emit()
- except Exception as e:
- traceback.print_exc()
- def on_options_app2project(self):
- """
- Callback for Options->Transfer Options->App=>Project. Copies options
- from application defaults to project defaults.
- :return: None
- """
- self.report_usage("on_options_app2project")
- self.defaults_read_form()
- self.options.update(self.defaults)
- self.options_write_form()
- def on_options_project2app(self):
- """
- Callback for Options->Transfer Options->Project=>App. Copies options
- from project defaults to application defaults.
- :return: None
- """
- self.report_usage("on_options_project2app")
- self.options_read_form()
- self.defaults.update(self.options)
- self.defaults_write_form()
- def on_options_project2object(self):
- """
- Callback for Options->Transfer Options->Project=>Object. Copies options
- from project defaults to the currently selected object.
- :return: None
- """
- self.report_usage("on_options_project2object")
- self.options_read_form()
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected."))
- return
- for option in self.options:
- if option.find(obj.kind + "_") == 0:
- oname = option[len(obj.kind) + 1:]
- obj.options[oname] = self.options[option]
- obj.to_form() # Update UI
- def on_options_object2project(self):
- """
- Callback for Options->Transfer Options->Object=>Project. Copies options
- from the currently selected object to project defaults.
- :return: None
- """
- self.report_usage("on_options_object2project")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected."))
- return
- obj.read_form()
- for option in obj.options:
- if option in ['name']: # TODO: Handle this better...
- continue
- self.options[obj.kind + "_" + option] = obj.options[option]
- self.options_write_form()
- def on_options_object2app(self):
- """
- Callback for Options->Transfer Options->Object=>App. Copies options
- from the currently selected object to application defaults.
- :return: None
- """
- self.report_usage("on_options_object2app")
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected."))
- return
- obj.read_form()
- for option in obj.options:
- if option in ['name']: # TODO: Handle this better...
- continue
- self.defaults[obj.kind + "_" + option] = obj.options[option]
- self.defaults_write_form()
- def on_options_app2object(self):
- """
- Callback for Options->Transfer Options->App=>Object. Copies options
- from application defaults to the currently selected object.
- :return: None
- """
- self.report_usage("on_options_app2object")
- self.defaults_read_form()
- obj = self.collection.get_active()
- if obj is None:
- self.inform.emit('[WARNING_NOTCL] %s' %
- _("No object selected."))
- return
- for option in self.defaults:
- if option.find(obj.kind + "_") == 0:
- oname = option[len(obj.kind) + 1:]
- obj.options[oname] = self.defaults[option]
- obj.to_form() # Update UI
- class ArgsThread(QtCore.QObject):
- open_signal = pyqtSignal(list)
- start = pyqtSignal()
- if sys.platform == 'win32':
- address = (r'\\.\pipe\NPtest', 'AF_PIPE')
- else:
- address = ('/tmp/testipc', 'AF_UNIX')
- def __init__(self):
- super(ArgsThread, self).__init__()
- self.start.connect(self.run)
- def my_loop(self, address):
- try:
- listener = Listener(*address)
- while True:
- conn = listener.accept()
- self.serve(conn)
- except socket.error as e:
- conn = Client(*address)
- conn.send(sys.argv)
- conn.send('close')
- # close the current instance only if there are args
- if len(sys.argv) > 1:
- sys.exit()
- def serve(self, conn):
- while True:
- msg = conn.recv()
- if msg == 'close':
- break
- self.open_signal.emit(msg)
- conn.close()
- # the decorator is a must; without it this technique will not work unless the start signal is connected
- # in the main thread (where this class is instantiated) after the instance is moved o the new thread
- @pyqtSlot()
- def run(self):
- self.my_loop(self.address)
- class GracefulException(Exception):
- # Graceful Exception raised when the user is requesting to cancel the current threaded task
- def __init__(self):
- super().__init__()
- def __str__(self):
- return '\n\n%s' % _("The user requested a graceful exit of the current task.")
- # end of file
|