commit 73e365d217c968f5564b62dd6c9c40b321f652c0 Author: Sebastian Zell Date: Fri Jan 16 11:08:31 2026 +0100 Initial commit diff --git a/.abacus.donotdelete b/.abacus.donotdelete new file mode 100644 index 0000000..ca0238b --- /dev/null +++ b/.abacus.donotdelete @@ -0,0 +1 @@ +gAAAAABpago_jqHw27MRSyNUPJoG6o0qn4oDBCUQRY4QTtKR1eKpFbn7oa_DuIOtOCowK-sQa8SA_6pBAw516Z5VaX5ljq6EqKzH6GQUHbUhXDWX3jrGTzZJChO1KoX9f2OlxxMYDSNpZMHPyHKNsnICp4-y_UvF3KjJOxQkXrNa4moGWRYGHXg9Y2nU44to6HtcRssOVilX8JJY6bhTU4VTEca3rMq0RC3iAUIRwXVSSfkx0S_h5X8TB4Yhsczq1xw-3g2DtmgeS2SgPaHn2J0Whv4YBqTJrSQhUY_5xxzaVuaqh_7Z8BFd_lHTS6RvmxoMUKJ1eZqWVF_smODJOI82sXFRvRlKeFxBwWaBgAtkaKxCKgF_YVFBM_o-LjETMInpkHkP9eNf8_ckbRXajzC3m1aaJoxXkyPpRuNv8iYJK6CbLhhhGZtz6_WjPPDjWmrTmOgQAHXdze92KtjQ9Jt61qvcXqmJjXc97zCoXU0daRp2OWus7_xViMmSy128kNKyOEAXubWq5tjrJgP9ExcjHwf1UfCyAyvdfKREG4sdTJw6nBS5rwTw0xAAiCdInCRqCe0FbfnhmpOV5dJ73ZJkzDW6KXyPKqdzvP7_oav6T1JJaygYQDQPgTMqKbxEt5c-cLlv3OXcHRcj5z-9w7GeWcCFkVSNTxoODLcDqXT4VuFSsTkT_vKbLNLlQ9F4h2ybxb9D0g21eYMq8WRqUEG3joP43YsKXib7D0NLoAE5dtJB7qR7lpLxKGDaI0Zwpf7Y7N5xvCjWtf1si7tbju5t3R-5wm8NUOA5ZH5GQOnuU29pmkjTo0Kzbe5BO1wDmyG63HiCQFUCYIsmrl8tsi3ZqkM0mlSj-5mJ6mxa-UxaFsyTN61u3umeZ05tgRN36bnDANvLyGRhteHC4F6yXnlx6UlymjS4HqcieKzTartBnRsXn-rPB9EJDoxmS5buWSXAPzvKp1hMgZzwoxccEoZFnewufurA-NYS-1s4Dik44vKbNcSJ8I-XrQ6pSB8m8SVoYO_5DsuqhgcFGlL7KNeXv5zvEahEqs1kShqLkCFUaebMxCuY4F8DPSoqDJUVMVUCEYzrcenW1hVvx3-1psbr7lIxoUW4p3db3QsMZUhRNDZwxRl0F8AN75dXneIC7jRPuuY3RyW2kWGVaXMP5ny5l7I9LSBcqE6uwvnp__Xfic5g_eU9lzrdANfXJCW5Oi5mGUPMcv3rpjBXlw6Ne9TrwQ9MfNHvwWMG9xS4QD3Upei5AzaPWIY4c8mFfSYDDraqBoiVOLB7qDiYHQdG-O9MfUcq--b2COt-RT8WM1MKF6DLGn3xXploExTzwrqlkmTEVWhgYhVfyEU5LCS7jcoghx8djm6ILjZFiL5Jh7oEdxk1rKQFupDBLnhb7DXtt-wVi5KHNrUnFUlek460Qo8sTIgslnU8xljdZb2yfnBf97HR-Y1PrecCJ83XgB75yHpjMYZylWW-beC03418yCLqcpstiKBkBBia03sGJ54XMGPzn6PB02EXhtc2EaP8s0-TA1Um0dr-3iFrDuGjRdHu6BNRNrVQROsXehZCqne2u8g8dEmey9LpHVRy3NxaIaLbM6LEC1J0NT4ZkJIOTrDAkeypbkskl0qLX_icArMgxyd5ayArJ6t4aje9hOsSzlgTml4mysVrdQnFKwkLqQoo7b2BVAg3stQTFCr101TRSXgtB5uFBvfR-AHzaYkxQhE2kDkDYTbp2-iMJhbhq-lq5vgrbfkmEfKS-qBdyeoNDeR5dodCg8sVnkxyhNSLB5k-paj8dOcpSHTQUbRNxxjAwIaC2hRXWb845YfnW7uGMnB8GCKA9i8rBjwUEd2MmEO3twKN_Ibq6B_N2AEHs_pQpbb5GSb94ZP_eBRx6VbcSEnUsY2Si3DWOfQwZbjxuthS_Y3T1M795yh2ybqA9iUDzBHtBvpL_xAr0R_9_ZtRYM2vD3dEwUXrHl7Sk4TxuG1ferQBovUd3M0eHOX_gKCg-w2Tt92jZPlsa2xbFwPRpb-6yTjSotWDaX4pzM8-CaiE90mIOE8UIPrwxmihjCmi7b8x828bz4hNZ94oN2fyxKQYl09EkWrkZaJZnzI6My5TZMt6KzagW6exM4vjSSQ5TQ6eFku2mUM3y7AXXphOe-jfltNePywL2faIRIJd6a-93PFubS_nEcPzYudjWQpZXnKSF7L_ofdKb_YMWywSNYi8DoSsZCS6-8sh98zOR2cidGbPAF3ygSneg2QOKOk4dJmhyamj00f544jXT-j_1EnijwwRIlZS3OCkJSz-akZ00hQ1BkpyU4Mn38Qkr_B6jhmfsIjopKr3lf-9mDQKoj1NL41RO71m2jgVCfiMA_DYgP6EAMApZfkvYBdob-9zR8naWbjgmRSkcTdMR6Col69ZhseSLzzLL-hncswUh_s3XKA3vtgAbgvjNDkfn7Ki284Hcktae-esEnombEIabqNuN6w7-D-VoH9mT7saRzOi9Ex3_m8BVwdxgs-N-51yi6pVSQ1ANNSbcWOgE85mEZsuAP_phf2_SeWWjnefcZtKEnGSLH3kPA0LXgdL3wSmFC4gH99bwWpyMDoGsA6ZQBCm3mgs0LdYtk9f064Ge8Bwo6HhhV0ICHO2t3piF_1L43pquHvqpFyJO0UvJWVJMtGWEuGQyosyZItsnm_dni77U1Fm9Pd_JubizlB6Yah5c7UxTLIlkgPAc6Bm8cYGbFESYKcC3A7tRcZcvCHnlnKWk25a1usW77Jfy1WK2kVNt4-aTHMLyFHohnY6ePHqieElLuCacSN9WwjjdIN-SGJg6akRSumXoJllScdB6SELHnUtvmZuxjDIXD6viRsOVOtIKSd14NfKqas10cnKKsqcmxMcmsdjAKW5S8XrzKLbLPFWaLT1Ji9QBiN-Lme_Poon30GWYGMWKYK2DtFmn9EFk1I1taBaGfBX9z_NXI2uWj_ckXsd5i8Ijnz9u3gDNFXDvNX1EYvQnUDVEOP4xfiDObWhF94cRxpdPDTt55xxoKt7PF05k6OIQZQ2vb-nIA2VP6X4I5MSHZ9qJkGGJxDS8TmW2iXRgVKw5fxMVnS_TDdllfKDnTYZ9hy85o_RqbndbXcMqKmDQFkt6HUu_175uATaEPuFZhTxsTaAPx5RI4lFqD2Tsa0V1fxZsmpYJ0ANaGRCb_XmUb7XuYh2alGkvccVEBV-2EUFTInHvUAmLOxnmete-Mh6WmSUchN1FTmN8yo1zFYgDFNHow0Ob1AIKIKFT8yg9xkGpyqDjTTOnWN4KihjhsmHZ3yFt9_h6z_MiwbY5Bpx0dAJiC-NYVs5R5u8cQEaF5xLdhtrhFMENDzoDuVtwwaAxeJFA9gvXX5Sw-PmiV6TDXt9MuRLM1Z23vRkA6WwnoZULdbJ4EsNbLjBq5L9AsoogXa9kyNYY7KE1waX3jJwzXXe_hXrLS71LoJoG_40WkMSwgMkOYZuLUXbjwcKYfNub39fdTAtDawujXP-UB0o8CuFhBkSsiI6jdbJwCZPJujwfZi-pZHOTJKoS3RmueP64iv9llgOWgu47CCNie4Bskk5fVXkYUESi3LoHLGMfzC2ugDchATSgp8OBtZnjGZat7xtPK8vujDrnLneSIFNKkbcsshgTuhNscFsnhlq55JTKeJZKJvLA7TbIIaV7fecwTDfFmUpaLXj4_X5nZaaWuqeqytjNeMBEs9yobSKC5PMwoer4EzGZ7I_0KBv6OjObqeQZpQvee3aPCcU9Lj09Pr2U_v7GeK0KLLEPeKTrVTpsddu-hu8dNcaj0u0ryE8eH3Eurx9egwvHgg_axZndMf_JtOHdvn0nkRkBxoGrBoEuwoNP-F-S9vzjFRKcNghnlBoFpgmtomc68QLwM3dNG5qly08fasmL0SAkMh094knN2_avihzRnOiE8AwTCEG4E9zVaMiF7VrWkXT5YmgaGc_TTcTer0hrHS8hnd8uL_m5YJE2GCDxnm-QvpAJQAI3W76s4uDK4moofB1KFkE-CJRQota5-X5KL97fFwA3kRp4kJ3SUADurKcc4aFMFCtaiSRw0_WxJ7aETS06AibCG8phParX1GRw4VqoQJM6mk9W0XADA7v714p6BgV4M2AFzcKWFcU5CpEtx3WzwtNOdm294K3ideW4CSVSSIGh4LieoSziINlobrw3wJDNgkxQOugc7YWjDyIt22mxYsmAnMCPhvwz5OGZb4aWlNjgqUnCmRokAFD5FjjSCil0v4ELosp5b82ypglOGm9vxSsZEoFkL-BsvB3c5LeXlu2Q3DlL2J6KxPIiRZ_bTnx2nICH1haSBkfFLGmRzb6I9kSOsKvUdZo8fS1jNLMruzhG-Qk0-GdTBJRG6KrZeltzPAzIQAaxFmCe36KTbQ1E4vYVVPnmqMh4YfQfAoPEmh5K9l7NlhB8gmlj-cNNqz5HCBKzNNI18m_p1mO1hVCY_58gS_vpsc9S6bcMgTRrjaDQ1by-b58Nk6fGJ5bkUwJrqO6_uROn35auXd9K8yqPUnBwYKqWG86YVmUX1k3xjRgeXZfpVpZ5Xrwj0jm5ZUcAwlUTjXWXl-jxn7SBcEYn5TOyr5ggcFRc8z_GbP_2S-mNZljinyZzK9YbqK2b6UK73Zf5JjZk7gU7O206TPF0xGTd_WgDzm_bryT58rZgrOLLqvPpLaXNlkojOm-ZH51diRByyVKL0D8IzNm6RVExNN5fEiJJ1-cCRMMyScEatb7aOYtaqtgDjdrPiJZacrJjflbov89b76CQ3sa1264RtWutOpiEl3GI5XTozPmWD1o1gqNNvvZjXISAfIyv8wuUzyolUu7EwvdLd5C4BftRebQ4IY82-BPFpWUcPWysVfQjJxTD-sdN6joGnx9wy42d0t_AUwc86WXRX0GIaBdg6Zsdm3Idur-fUQsT-l0jvMK_TxLCiXTkP7vTUnJoXXMgQlnMvOTumUN9J9CJtvldOh5X0zKAlycwNlk36ru7oqdV7-TOhsQuT0vHf6zm_VumZW5S6Jo1QqOfDvA5kye8zzvoX4CT3j_QKUOMIEaVjROcIIlegWDx69OLRNsqyogCu36TQfH6OycD-qmsfPLOFh7wEks-csrgeI1EM8ISVVXdn4u2S8OvreNz35v9b-rhR9gRd9tvyCXT2EgVsToxRndxUCz7kcInUcr89GrcPSXyDc_4-2UnhslHYo9H8ej3NSEPo7cpG5tMse5HwkXQF8lK8ibdZKd_zNBUY_Ih9TI4pBnLcsObwj5Qy4SmU7Fz7e743RBRolHMqhMBVn5BSVkpdb8r9AzyQSQTeM_BsVA67inIxeMw4Q4ckF8nCHVrKR4ICnKoTTzx9apyXWOiBFIBtH7cBjsmjojmgHmkXOL-CSJ_i__DwpgEImw-OB5XXQ_vKb-mhdYqrrhcXnbfpwX4V0QjicjGIjTwC1-zG3bckUBNBMLwCkYnoz9gTJMzZvf69MrCCcRw_gPjF1jRmu10_Oy8axRVZTToNa2GbcqkGQNW-oFq2id-JfSsbIMStPuW59XJLJIPCpb5UyQM006O41IdljWdCT6ROuu7bwSdI-f8Mx49gY00ur-fHhQ44be0je5b6QpnFIis5tVxag_qBxM-fwN2QJwaNWdI-OdKrQRqiqOVw4KYmYwgfgxGrhgnOba5_xA9BzrU421fwkCpbjhrrSlhXw_zcF3SOF929BmjcpAIryAEBLXfWi8e0A_TWs8MxZXAGiAIwr7--DYdd7H8v13pxu2ytMmfYqUHoG6-GQK9qjlrNAVA_4zNlKnfz_ESMqiegzuWGoH7un_IzD4N4smG07Mt0OoqEiPKyaTuCXsSV3Oq-PIWy0otMNh8vnFMD7Oq4BQEYGY6bafU1FG2GVTWOtnqIenl2rTkSf1jblL0jSsZCylwG1vB1dkWayU_OnqBGOG5VmCLen24-dzXfKggkM1sycW65JZOtJ1fihBjHfkBxvKj8_jVxlER8FkF6NLDwuEl_h0wigL-l_hzW0OahFeTEQ0PtLo9RXzol39GAGY9N2w9FqDbhrQCI1gyUs9BAQuGY9rKWlJlYo4cYy9_uqSd2gmyOPoByJmABCgR_y1bQLS14yg7JPLygIF659ChwqOlcZ0NvxcIMI5iZVN3gmwI-vencOuQT-Lr-bT0D6VTMpRt4y1shRjTwkTc3J9qns6q8tC9VocDrtrspkT4wtHhlZWCRcLldWtdl_SOemQV92_CcRMdCzFPbgt2UiIqIsKEFJDSUpvCJLNB5B61qqy32ZBihOw4t8_BUkl5upcgKLYYWekKCmSD9CXOYpNDmpvVSvArVZBJOFHwrrtkmh_LBzx3gpS-gd8sM_buvTgGmNsDKHCPiMnJeKIyI7DGiEnvMvq6A5gPVqU4rg1odOncPvSqGnJnoG7nUaJc5yDyQtNLj7pzqDBAodWENPgl55Wm9Ovw0k8mKIWT-ewtYsqjdwHE1fWoahGA2XKoVad5Bre7-YHWyY0kGQDsjFmdBgGuVwOnn7rJx6hnN4ZHZMJ9PWQOpm8Cf04xbASmBXJ5G81zavTnNpXcyypWcMvyp2aQkGfB1gRciSac8v8heDYbNxJkkjrjyRt2LGlL-475S5cPj0v8zL8WvAXbKb1bWxWi-yfBYgzdWQojv6-99c9MmP2oah8nfpu1FwFL4CkuTZH7YLyKhHtaI_RZBK_aFJAOY_QjagAopYVeNV-h2F8AHr-b0ANZcvMKW95hkcZZVGMNChLHkJKnsZfpu4vMQ0EL0pk-Al0mvjcycmidHp42MIRcExKTRymMnvtS8FFNw17rgPAPmM10SBrR8QerqeqVNhdSS7E9Y6XOrX3wYVc7eY46hPi-U0o72qdccyqIyxTuZn7ABzODlywijlKXs-aCO5aMkaG6J4YGUwhp4r8gEW_XeqodtFgsidtSW5N6PiR9w897xcJ1_WUp-glcwsWZUZnVPio6e7zIVre0telCcdOBtur6xLGhSL4i5srrv6aTLambRnc0X4cbZ67Ud0AYB6lpTLESvwReJEPc3uNY5jeiltVjHZ2UpaH3XZpTkn57TPbrwszQOyeCj3QrFydZK8KBMedsEmA2VHzRp5Mop97pAazPmcud2547qXKKrUIZx_hzTVGx1H-7YK5vZnxTgru0TWXTuNTnleZDROONaR14TZESpt7nBsV7RtpWBPjO9fX4ug_S82v_DZtXkNUPePZ_8zT6vUMfr_aheioGvFPNLALariKcV0tVHlnaQ_eo3-LHvL1PVjSUduWa-s2vPbKeT5hylXqr8r2INM6BdsLhk-0KgeiGWXZqw-1PXhFuBNRS3zukml0-6YJ_g5RUR8bTj1V8OBnQled1F3s7JzXdDwYpJE34XUTukS2gIIolkU5poFxKfJHTyeAv0ruzoic-14YTpl0ewWhPUx0cDsqWvx5qUHcP4X-gn9k29tU5zpriD1YOu4gkGUChAgewa-g4fjQa5NE4KgK78F-km7gEQjvXV344wNXmhi-4ScUGwXnRfcOuMcrKcpIjmZQklnzNxKh33iFutKkFbgzAQuMjMZ7X7HeaNFVcnXZYGNr_j2FhpSiTVI6tBk79n1RCzvSda3XGt7BDo5W9P22su3G2dTr4jWJ49yXFnD6XdS5xp_0WV2jPPgP-aXAu5PWyvJpSmVCpHoJ7ZPGoGw4V7kEd_TRbHr2F4bduTpScqPF60gHBCxe_bGj77IRKyW1jrPoZbovMfgiHVmV2YWMxx367beRzyDcGFmAgBBO20RGfOd4tRAdJX-0Sbhy9YOkHHCQ4W0q989OQVoT3DvJj1xRAc9KeyMFg-gQGTh035aX_QWAd5xamc93X5lHUa0V3bW62krcbXECS7by1Opx91iitXq64TDqy5SDJqWXAHdgElS5KRc3P_m5o3hzN9PRVHFXKN6Rjl9cBxv0KJelwh7UhtSGs83WpIUveaphNW_iEw4kxiXoY98yMPBg5_fSKJfiXW0p8IlFHPeeb4vCSIr0EkHwdXc5J2uO3K7ciYU0FH-OkDw_sA3PpLZ3g_rPikpDAOUDn1Q0eb5DikAn4Z4PIbW236UqRHusfGda8eyrOHUr3v9TBRPiMkofwjpGsJLtFnc0Ljzqdp0SuYeigALBLwlp6SWkoNWqdxRUkjdsbz8YkDATUhrE4Nz0anDYcIPmSdtlN9V9wPumGlbzUyaN6eX-R61MeC9-z4-tCrG-d6KXb5_z-SK8ijScSLpMjQLfbCWqm14SvAl4XxwU3yLRlfw75jciHtUzOZFWLuC6ar_BeEuem_aNGzCquEZDsjNUq409-0wdTElwtPSOL3R9517zMgTpdS4EmRSfSjEa3sL3posaIgKB0pC2kd1QVJoYnYQ888Hlaaf84SYos406w5ExR0o4SBzsx1I8uQ8uE7IpVvJhoqFAzO9TNZvHggS6NqQlTNPWyH8orcYuCaVJ-XVIBjv4fosGOJCfL_5RIMuVmlrHhkV5ivaQJHYazBuOKO85MjUv67hYkuOck8Gq2Yjf1BBRrZXZh43qfDVAYI6064ctJUemx6J4pqmzOTroMAjnyeXKvffn0tFpbYph7lHvK051zPLV3NXc-S7b0AcLJt-h_R7Z_J8lsydbMfK77YMoY8hWLafLX_kt-CABpF22vWIJn_L6aVa2uAfmSfhDrPE6fodadZdcsB1Lf3Enp4L_d7lM3y51Gxhy1TFH1KZzsMwfRIL8a2MmQ-wJCa4vXFKgyeMsmdnTNGKftFHRL7LuGQ5NEX6-NHl4eeTEBlJw6YEgMRpxs0tM8CXqfheMQcbPV1l3SXPfVv535pEjahnEhAZSXRVcSZYWOzFMUAeC6aQwTekP9h52DkmZQCzyWiBeC8Ow3WtD2qVD9wJa5yFs78B0qvHZa6I0ZbTpeiaz73AWBtxDx-JSppYNmBJkNLdkbEfixsIL2cKpGmPDBI_HIZHEoFGcl0IQtfY5RPwd5FrGjfpn1CaQGmceKzd-tbfxb7QMfI_APiTgoT398a3lHz0lpj7iRgVz-9kc5JtLVFx4JHTEHeng6RE75XIlMsPvhVzlo-Mpm4ALsDx9R-AHG3OIWRIHIQj81nZD_yudilt5IVKfWBphQNB4eR92DY75Szb_QiB8tdGaRZ00zw2e428wKNGgmQkHZYfBZneYOcqT0RRxws1_eZjWyuZMCuKG8d9tSX95Vhf4A9bIlHLQl0JY5oV7ai4_gHMMyjV9pVb2WN70mD5nqfBGBRmHWVxIHAwd6w8JG-mLVAofgXyItrIQ-BduB3R0LX8Mepf-u67Rh2UUseNFJTyVJ-XMGS8Il9nH95JIrjY7EDidek8w1yIS4nbrAAyoGtGR0-4udzWuMHcpR2CD3hnvEl-YEAwn4yQDOmyQB7P1bwCUkfvfVJIwiUSOT9tEqCY5Nkk1DP71xuty5_r5kuMV_YrDBv5OW_RudB3BINMmotZWGEW7SElVYGDIv1oS--AA5Ta-9YTxveSviNexN4ggecsBx4GSjDWsT4n-cP3bk_HS8TkH7OVNcBYnBk9RG4CHc32nAEjX6Bvyx_-G4kUhN3zWc3HHMq5_SZfk7yF_XqUZWW_-Qm5cLaTv3Gr5GGvW-ipv0Yjt0wF0TQvWvQfmFiIdpoFhklqTi6xIc1ULVzXh6oeudZDW8wk3fNenXW4FUzsJH93yodgU8yme9X3N9V0EZn32piq9XTicaxg34AVEEhvVWZTFA9Cvym3_rvQ56LDORdIUc5j2KgQKTAO7Ti048k6cxvTEITdqTOEuvFTQu20GC8DXdfdHN2AFAaQ7LybJgL1pTea-Av9jZGQQFJoOtg20BMz4DSxIDXTDflefUnvgboHdOmKNww2wvvwtVpAlfxJWV_6ysGmPgsmtR2mD7bn3nI6Oni7hW-V5pGP0tCxCFsnW8int4QcufsZ9APfmfE_NI75bY1mx8rvdcnYidrbOF0AGssvQO_1i3EuvtKZeOVNJkccyfp-BrSC65DpUsEwsdtm_o8HUnFY_rV2aYWb758jw6JEme4XuoIg6Yh_JN2QogJXe_CsT5W78KmE4gwGDRnyXa5MBfS4q2WppiqgOucCBKGmmzOcbs8FYtEde2qqPZKPKwpUxCO5sDL8J1PEJqwFaUyiZV3-aRCPlO9OU58mU5Ai2Kj7E2cU6LBZ87u2Q-Ju4eaQS5ZqzA2sD8JwSLbwHCdiQucr9y8TkyY6LYIGGBuojaJpIXdKc13n5ThvBO_XhUb1TZ1qLFgPiOQAo8F_Ku9agClrPBO0EQB17k-HrU4gg9mkg_tj4SpSxTpIs7vF-xUxQzseJTCdkMN3OA5-MzH_XOX4HHE02hP7Xtx9-k3VCywU0_-ldQQEVC9qdT6vEJ2ZV_dmI6B4h7xdoCw-0n9gyTceI8E2fYmKM8BZSI2lFW35eAYrstBPwrHtKOchEUKiJVt8hAD7SQe6fXAYV3O6vg9CU8FfBHvTw9YxqFd1idNM5ciIHvEOXPOL62u1ujiPdJH-WsFyWp2BVqO2xEOVH-jnxN7nx4p0hut14A-UT_54T1Qc2j2MsYD4U3aXsajkLli108dO7txFqIFSyJFT9-XBPW2BNe08UyKGlCPFG23sAXzNCz1amd3ukVf3oUfh6hSvXI5x1RWo0DO683qLZ2TL1DH0hGvOsmgESU42jCCCjjRy4Pqg91TmvRtHeLXcjSDWPQLixSj9FXPuiX0SGdWXXsLDar31emGKba8F6-_v8widU_gs_oya0paqgewJZEpgGNXIDSKwRbSyyEbYlimFvs_fmPa1Ora8PzbWA72O2auk0EZD5vaHFAts7usi_WUgAr_UNBo7z4OK04JUQl2AjVUDdDblvEV2Jn_b7d6wj9ykXmHsFRMYu9S-BGxDLhvQ5H5VQOgUqY-yMM9W18rS-VRnv3-kyV9-Z7saS9K-4mWcmhycXq8ccyI3rMaKgpDZCh5-pxkPjzjNWNlj9IxRR11lUEV_wFtcAlnr3fD9f5HH_uKOWnCFcyHmBF-5uuhqT7SwjhVDw-YazpyfmhMjWkLm3mpbD2DRvKsssc6wSJtVSFe_szSJF_ztM28Pxsu6g31z7gkN-ED6fVWk066TBycWgV9bqjL1Hgega-qGGeJrW5ADXvcSSPo-sWCKAB3X8iEicR9YqpoL6AqmzB_bAKWhFodP1EEkYG44HPdg2KA0bnV71BF7hl1xJ72FVSsvrxQaRUTFbzlPmoUdi4O_tqSqm4J7dpZfBJJ4Qxl6wFRsB8xYlr69_FZTA1-TTYnk_oetqFiBDaOb4RnceS1l6pGda2oDLt5f9p1lIlLWcDIPiUiLoQjA5yVlYXWRPtZ0MN9UcAMtV87E6CBqL1vEAlSIUEA8UPvpA03AAvti69J7GXxmrS0Yef-F_pGrCxultIBe3bHSJ5JjmIWnc478kw2YH6CcptIPCSVuUSvK8PJa3mQfjlbAKKquiKzvuyDp2w4zB_FRG_qy1eLVwjiOo4aFcRfYXCqq0VM5bfMaGl2tE_4sLHwG7fUc15FQxefuwtW5Mfd_y5n1RBxN2-1HDjWBGsV2fEV2_iHWsEn2VqOrTq7yMMo_lv-Kq3DnLJ_7Q4WX8303a-iyPNuvb9_OMJkcDSr4kDnhOvcQ7vOiocJCl0L7SdqfLg7EGZGc3g4qQxFIYJNORJi5HugpPhDZ4mRvrq37HIUpLXNTbXYou2wy5kHUgol77QyzZuLEm16uBHUYdAlJEyNLJw0uJA4kktLQ86Ozl8kOc-6kmZKp85ts0Ew0Xchs1OGG5cfVHDH-XHZhlgksVW-EHBl4DBrzIhiRmivNqtR-3MzhoHMh53B-gS2sgznqXJ_qBRGfClJU5o3zyasW5SL6j5kX1fRTjTVSbyVEYTKURqT0Dt-zQCrjPFPVkrr2NgW5XCCRqpX3l7jvZZ7rkEqnbm3EGj8I-NaG2-hwp9RZLcwg2S__kVQOmTdXA4PtGqzAvsJEUU4kbPDb-YJ8LHyO3y3VPAMB9A1zwESQCH8l2YILklraRYTPgdrD6pmmkHNZUdKy6Qyjqw4RiU_TcT5xbjelw9_WYdxxWVazxP9c0PC1KYj3Gcf2tVdK42a2zs8p00ElsSvVBXnTv2LE-NPjGfu7Q1u1LN3zP7Qg5t8pGndGtorFNnKoY2vXqaohORdmaQsieiWLTmBtFQbmzFkm8M2sO-ITtB0ZVcaZV6uAwzS-DjofWsEXMnjp3TxgJykbkvJtyg2L-sfxe9Yh1CGgG07EpqYWzv-oObR_4GACfBPO7i2P2YNXnMkEb1Vy4SjDNtQ8xc9PIOWblsx715ilFcKMhiRBWrYyVsLGc0L6aiZI0aZKX3xyItUUr3pG6IaxOZ6VuPbxea_PKutfCcaJIuKU0bS5LBZjM_Vu4NMaTBoVAqT0TWUm06xvkpb1kqleZ8xdsXUvffOWbueaOD__Ilmoqcbjr5tnXejJy9lEoNOc9KUy4sH6JBK-x4v2wGbXp55_JiUprIHKoMAM-A2XbiMFk4JPH13xaNBF76-r8r87JHAR2xfCer0sVwRJ1-0lipza7omsivqlccmLZU5CqkfC_oiTEMmBIVsTCKegFr1EKRMrRzGMUJ-H-OzpHMykV6ktizXn9TUDZrXihrnVaue7qVhf_gz6IcrCRu3VEDBW0ApnRKr7ylDGx4WVIgni55ZOj4YT7rE018Nrgl2n3wWWBh-yvQ2gwYLvOIWJqiy52mAi_Uqw_mgGHwiu7JtVTSNuhLMQGsXHw4-TJmyWR0PNSD70Y9Ev1mh3WA3XDvBuyuQLIyj-sn643_e4T859kzP6AmLh7G0125SqlAAy954jJRf48j1OpyO-AoDzuVateFAevH_efqIVW5ohRP4zkR3GSCjfRlEX4WjBnFWGxvGq4tphqtEHYh2DE3IQTRyLViOWfS37dYn_BwajzNSTgjhYhn_7ZRnDKiM-IAL45bo3E2yXwN6zF0IhnFjxLebwFUX1zrXSrZJkWcnuojN4Pkcw4xCnUEPL2SBgGa--4Gbq9MCWr-tE5Z4VCrtBh34ZkQhIJJixQbSILOxMT3xYBsH8ByBvGoHdBP1KQGoIfNxb97QmHpGl2LQguj6zA2FQk30PU4QNqTOLwH2oInIYNuxG4cXDvMebks4yCbpj6zoAu2zuUj98WYIwpdWc1h7t-qBoK03vQvsj5lZyxMlgsfNe2JXoMPdX0Uw24Va_gIUlf-Rj3VeDGX-v3q_v2ynoDQUZAvFDXZGrpuoeoVyVQygGriWvJE6yAklB5loZjYyNoffJ1Gf4A54G-Tou5KY6M-mvdy3tmXeooyfrwg5QyWg-h8roKzMrGkT_VCsKoQoIA0hX6fOLl_43-hj88l9MEVu4U4pY-5SWbSzO-jeTvvmHl-KR7LaiLqzOA7X8Y8YtodTqKStvvAcgvpq1qEA8U4ns3IVFN007lhn9sECPAJFIMwPLQMjdUcO-IIdwgxZuC1JD9iKN8sQ0lSzlkpGhasiY6z1WSA4bd_edCb3gZ9GsRWRflqOiRXkC8VebULxAG2wYRBXd8y2Dc5QkEVhzuZxm0XCJxf2vP6MOnxSnSGVeZsRNqIpzQVoZCuFX6yUvohLCWokGtpSm7tnYqaWgIGzKPNNUuwtmw4aoiKMt6cuzRtlwJVsKEBkRSyz2kafsVcps48208N7OTk2utzZn16DrO4sF2d0cpEdGU3xw3Ug3cAC4t776bxErUwSp0iFDyN6alexh62444cB4SrdNKAsUmFwAoQwhLFnRGXYzuNIIXbAjXyDJgD-_1Wcu_NP5t8hf-PLtLujfJU9QgCzpcjQBjoI2SDjayLDJMiGMHhVeun0EHAvqrmaU3IIblIY9im6btj3oVSLgT33GgimLKRUJdn7Kv7MyZruHmlwovE1WilfmUC0WN8ZefQRLZes0GU-841wD2GLBrzDvzjlMuBkuOb66ayjpU6TQtLmKh5HHv23qddpsVDQPtuTW0tfhx9lSrunRAiZDsh4hXBhh1CDOGH9NHKwtzeEH308Hy0iCNm7RWjuB4d51oz6E78BH5y_2SR9V6Rgt7jR7QhRiAbXV7KQmMkQDeq-gjVhqOrMnpQ1e-hglYdMC1-Ld9b5oxqT0M_ZgfMefXi2dpsweYlSwrUDllBtlcgyVnmoZQ3L0llmOLkaplYDJB9BGj8u3RdL9kA4JLnuO7zkrJEPJvud2u4XhyKdZYBCxXMhg0L5bqDBCvdPsnV-ZckroofXZdZu22vVrCbMgM_Bsg2hj5HFlTtLdYeNFF8YMSQHb1wpJpGEro75EaucD0__zFhK9XRFp2flbcfqlLHpu3osWoEKinHWAvkCnkDZ6cRrE8k8MAfGA4J8xe0MOC_RNqB7Rgtgjrmq0WaMSPdI5zcds4LUj5Y3TsfzpxubZKC-MitQvaMvrQHXLxO88mnt6A9mOFFC3w8NGApfFsRqqLr1tz__ADDZWPxrlGKRNwK_oqhNo9F6rxvyar1vvfEpFhJ4nc3v3yRyK2H4US1SCaQivWJg1-VZWjGaIltI0atW298r6G95xpQN_dzhxN-i4MECdju5b4PJK73EX75li9qG53UkX50bWWUqc6kug8ejuw5UTq9dS32czHLr_996M6wMPe2BAUMRpo3F4qCqrI9LH1F-f-w1xKZwsI_5ntgh-WTtPaZc8VCdhSFHNovfZzGBs3ed_GFwObPDWRQ6pHxqATqX3CJAbyLg24m4aTuTUOV_QNDA8snPNUH5dp278gD74wlnStMontub0L_1OgPFV_FWLmh1jRfuGZWdBHdfnyQSanAw0lDwEFOaJtxN9ggs-8NIOSNRh7TOVMNyWSFkAc8VsEN_Qb5Ji99IDuRYHxs4WcoIZby1bq9NuyDpLfQ1bZXEHvssvPtpu5ACwLRNF7cP8NSq8pY-wVfKj2xbfIeusuLYEZJBQJq1jLET35852AnC4MLGSiIkSjrtKqpzwBuHb0rU0_FcZxbZR99ia_adihqDksFQJMfI4tcECN1Awls9M9wKhjhvNWQmOUd3lCBOmKUQJHQpnHXtFCOMWuvdGs3Z9zX9cMjkUgxuY33p685GgdY5qkORp5TBIuASRuErsmphkTvCnAG7bp5Zrn1-YQYYcsz1EzkXLDfDs7oGiFug-J-2WVNZ7Tlqnxv8geGSQ-5ZErui5JfYlI0ZyP1OYwEM854UjynebY5lqlnFaOGq__BJSisVWENQJ2d4_sGdVJnMokg8dbHdAPBpwsDBKitxUH69Vo42LVOuMzQvGOoOjNvah8VUZDi7gH4cqcS4F4VzKw86nWZjW1v4CrmBUAJCPYH7zL-D4JeHlnWWey3YMP8oKUEOxAIe2FiySnFhD8k_dl4q83PyqViQyzhDDPEaqOgDTGKSIal2poK94cxnGEsdT3uSgqZD2Z2_VGXIV-i89xMRLavzcmghsnu6xOG1sarce7lpAtBbE7LBh8RDLPgN913Fkj6qAQZM_qMINwOlzUCPbBMYxnhfSBOclh28wM2Kw9yls9mhap8Cmi4dH1uvBBP9sw_eizyQN8cMFGSmkKgoxq92C_xas3k1bIMFuwVFfqCSJbJ4NK-Ca_gNSgSNm3fea5QNgWrQMzd6KXiKE1Gtf-D8sV0e4YrlPefWpfiVqtdfc8YxHC_c1MXm22W2lNXWfNEQSL97fuH-NJx4rscGOqr3joTe81W11epuf0PWdYhgPpWFVMchNNwBSbeGelmdnJe-8nN-Nh4Iuqa0z0PrC4O9WlcUqeIghMcEs0uxee-9d6JmoBV7weRuxE0mjVxKXAePVFIJan74MBWMEGz5FOEtfq3yEakWsh39vv4d1EyuEAyMRqHj6JVZznDTU-xqmmFcD94CU844AtBVNESoGB2i_vU2YG70yxfXlZ4A_Bfv3kHpaJB6m73fNf-ldttVNYh0bxWYrqnEoKO-WLAYZtCU5VY8en80OESEiTW5cJFe8ugvKS5Pa2wbyEK2ELUBUzZpWZhKP9FXlPBrfANOV0CyCbFGwTMUAO3hOECel9IEA2SbxGY2TkAF5ZAdTY65TahUJecBO4pSLcdzYPKDtebAOXgWTIzHndbxss_mxjrNDT_l72j1XMOipm8nvRStX8QzNxtq_J3xHPSIYQKX30ieccZwV1Nwq9SwDLKpaAWor0lgeDhadLzHOEcC-Pp6EoFEAhyZbuM6WJSayPxdItv_TzeeB8hjfFfQ9JdEY_MrQeVzDaSbOmQS_c7tL5GKGBugnxFAm6nzjlX3vD0zhQxD6zuQvq0Proji-H37zsiC77N7DrWkfCWe8kF0O6M5p0P5L6-TGjCUmpykFGOdt-epT4W_XJLeyoq7sZYDbPWKomt1HViRkpByIbrH8Mcy2pKcgtyfJIhyyoIHvhVPok9bb3a8vhJHCaN_4GB_DxgNffS0aNm8J_hCkT65HZ6kdqdrCtRtfueEcfxdtU-35H8PaetSdk1IkZwpoSBRce55jqWLp32I1hYTTxcr_8xtqwhFYdKGGzR4INe68SexLzH0qaPKp5cfB8X7bh00_G8TyVnOYZrzt6XA1TOAovJ1nG0rYyEu3F9sVz10y79v6T36l-cbL3o8yqWfXgpfwATojsTk0VHm0SSCmWy96e2tygz-Vx0v3KIlO3gDj4ifaCH_ehqYEmyScJ_994Gfr05b40l7Negr6END2NUv3ZhWYmv6dufKPoEnsQ9i_z7Of1eDDzxJogGnP7k6dgUdV9KKxfCcObTCnCheYvb_foUR5Ls-cdBrRJFc_rAv9NP0wosNqMmE0eg0-uS4EMxqExSro23BoYTrMAuYH4c5JbEuCvFlMYNu-FHRWbtG_xL8M9FGwN0w0T17xEHho7C0EX-MhXrXQmxkJCUvD29GjViFbmSPNfS6_3mVKgAqw2KtodvA6NY0xG9ArL5u7wr0LIuo3EgANSVU3RLe6lQRngL2rj8Eqk09J0_owpTefI7qKEE2TRWE9cw9akSwirs81sXGuWtKwdY--XMIv6nEHpLcBevl8v19wQGXlHgscK1V2DyiivziwGarnCCcvds1N41GwXKz8hYk4IsS_G0_kDSUOpi3iWukIYG9EGkxA9DpipmMxDZMlKH12YwtSpBQQk593Omin0ghhria3MMe2Ced1uZtDnY7nrKV5SYCD_gMfSGU7T77__Vmpdm2fyCAtPCBbu_RxIjmzZuTElH8bxYNn6BT_wKLMJ1e64okHexH-wT4m0BzCfRAtX4jcYQWzTZquimLFcuYnDLS-d_zIqCah2q87wH6P_4h6Q_Hvj4ZIBXWoYkIzOeXayoUzXMg44qrl_Nt7UBNX_9woVjKj7dEvlsGE89jrz747UiESFoJdUlY3_5JpuWZWRus-qNuhSWA6xxhJiQW3AYTXaFpMsCNBtVUWn8YIo-xe9gsaN4Lj_LcfC19VIUkENRhzVYmfI9wr5RwDWiDVAtt6pylGZA0cD5Pd9eJE54PhaBpq0zoUXQhDQidU1RVsVQDBXp8Ng2Yjegp9rV7LXJKQ4eKi8sn9ExwsVrVaO-M5Om4NpwPgyJ5vSfIwL4VIrNwizfYnEXKgzqAR28Q4qLI9IR82C5iZHAy1hitVIhshPQicWqhpbQXRrFDhoE_zcafZOdINHJqJpcsVq--uGScgjoAuP_y5y3L_RSl_LgOvKwViCvVGG1NX0t8I0VPFxOhdBwq3z2AKgjT2y9SsnwWcvlrTd5of9Adl_fHQPUc3CNci4zeyXJOUOvuCXh7h-09GiBLHmDAlxAZHwczusfGUUUVBZVFf2mN5qAkGOasw9r_1wATuqq00gE-MDEfIO1CFUnG5rI5xP3K_292d0d4cDO1AoNm9wR4Mre51NT2qdSKSFbg7TlKFyngCQkjDYQ4l_i7A1y_BYN1MnJz0br2kfTSvayoCWtQhObMmzHJNiiE6IDd2lwEiA9gv9hvflPDyYCMV_6TXYIJu_mB_0Ulfej9kdCxY_tG298ZouQkKtOAioyBBb8oDLBIJlo0b0GjcbzusWhRN97Q_wh6-n08tOAkGgYONr9fSQR6Ey5tG93pt4gzIzInQLl7uOP8ZhgICUA9Qxda-SiayT4W08T2LZEmUkubCfTZXApFYTwwX0kbO-0Gj66PRx5nQB_LHhMIs7CKJTbkkNMnAm676S_hHzeV9hUGPMs9BIHStjqPvt6Yh-XW2C_CRnawT9jWd1w4PD7IPjuS-NW05-x7B9ecRq35luTKTeAgD0n5uwjST5RI933qquTORul2WtPhYfpXQbogIOD1qZWoDm1T0eGgPXFBJcENiX71XZtXA1fWXnS3y4LQ2e1mhHgjyrub2gj7E4325K1Ztib9OgzsseJTPQGbcf9tbamgMMuyh29ndz9RGg0CVHq3TaBUjZ7i0DkeZrsJT2lDc3iFgFWKoWa22n7DXSmPypybaJc108IrAmDD8_JrUPZVYNpUZ6kldr5LFZ5TOTVI8YrINzpP52v6Rss4oBjR8ksK5AYNvPN0muLs3gmkoIiAOtQ4cAVfyK2pXqjpvOlBkB28S8t_rcS3y9GkvzbukMQEQTsgbZzj1052QQEnw9PsHWnCMQJ4sRbRr2zSpya1wl8xQ5otL1BcnSX9pEYwzjBK6cxEeyGbue65r6fWaR3LivJUxDzZnttrlb01R4qkdGYll6H8FCnZ3c1A9aMBgEGZDOEpSZfKD8tjIyxPgB2T9dGtto9JqpZWdiiFDIS3FIZ_I91IcQxp1uLEEqZrmnhfaa9KK3VSHA_Hbhs148x5r1KcgoSwQSdMxUzIGCof0G72Jeb-Wm5JgyTnq7eyOXOZkB72qfs3NqjEt7547aFDITF5vz1pfKGyrXPhXUdx7Bavysy-iKZgUh3PFdFShXRDpMIluKl0-BqdU6ckjjnYc2K80PtqdWyADRuk9UvZCDG-xrvtrI-I5rIiv2aSq50vJcUkNrSzAGKut2jPtFeQOn80tROS3stFkQATrKE4mqe8u0wqXDvc1DdRY8XS7aDUh34LySgjWvFWjS57MavQblB165XEtg7ATmqBpf1DsbnjtE2PJ7u_pKDgNzYt3BA5oIKs-UN_QDmx-WSRUWyvRRjxvYufr9mwj23hfB-mBn8t2TK8feath3yUNAtd_wUzSUGnZqa8srk8UOw5U2Y1u_heJRgfesufP4EklPdsD4uoUV09FfkmdEZ9_69eMtZ-ZFWu8kU5MzC9ITvCL2zHlaBY15uFCeVx5C2GRKWp5yWNqfw0d3wDipkfT1Xn9B3DrnpbolVtuuEloCS2OOKQPOJWyuXYELZKU2x1Baf-Lk3_kHukTvjbf7HllQO8wMakkZ2QaoR-5bT1v-Q_vtn8BKaz3LZ2ViWEUwKLMQoFdr0Tx0l8kkuguYAik1M2JUmY5lkfjLozQuVUeCovP8hYd5hzkUb-F_z-0uMhzIATVEwKM2c3gQQVkFz5chh9Nllu3MVM5zJZJ9C2LsTJaG5e0z-lRk1LAnRAy6kPxu0O1fT72dETGq_t1SozuTivQxnIYiDKb0WwSkbbTD-1FoUnLyzupMlAjJN7QbZ1aVEX9TwR-vXjo18tcuyxoctGQE2xGZeOc8-NHj_CHcGORy9zTmSFVfs_zDI4VxjC-yVDmjxc6lGzJNGQUbVCAQy0HkWui-tBOMioyftI0fYIoQBuQ6nsbDEGFzfBhZDNfHlq16WM7tMQ8xs3AX_KsdlLqeKYcTg0oDtREDUNsGiXM5IxWzuMjzDw-heJIncwXd0zG1xz8JsutyzXOiODcFCFUl2tGImsf-qSl5fm9ctQ_QMYqj3n-LgsBeY7GbHHROVza65ERK7toTYx1VW8sgZ2Vwv4K2mD5pfoUtD5j-wwirgeHuBta5k0v1CuxkYePxoYbI6zT_Ya7NJtMFb2ahfhmbQlntZh4jqgYObRdZiru8ECnNgAboSgDCRtTF3ybiQeUHAsjMIIbxR5XNmgduSbSalngPRoEGB6Q6CyGN6yRApyIOfHf-RasO1-Kh87SLhqJPZpBbxjzrDzC1jxyg1crzUzBlu5lWJOkDGHPIzvCxU0HwgQWmOYtefhtO2riiPkw_T2cS1oZnkjO9hm6mLPfJdDx12UlJEjLLbvjh0egFVgw_G0QaYL4O9ziuL8vXmWH4-IcEFY3d6KrJ7CmaGU8rDRik61GMXJrlwV5vDEJsLS9bW-lfZLKTeoNtk05VBUVdI4x2mSFpYzbUcsxNIbT2XA_q5pRSoDM5MOH935hCpmd6mvXo3R4FLO6KQY0-913qGsUFhpivT3kbw-1FJ-sOViVL3JilrMQtOzj0aEasoJb_w_V6WtFlZj_gZkDDWI0RRl7iwqYHIM9XN5EHRDH_drNOhX2gITWqdnkshyfzv2VZFPbGph7gbhmBEu0sSAEHjOvH3Wq1RU2yxv7S0tI5GOPpRfYdKqFhw1_zMb0nRgwR2fJpxwt_u3E4QeKHz_B8Yv9DjkGrx3PBA3IXKh8Y4zL7tQnjC_tNwH85FjIdEmrUeAJeiUe9_jH0Jy5NKe_ZySbF2EE04_tz5scPE21SEm0mybOCv5nDHKVSzeZUITcW1yA8QdqulZ_0xiE9aNXtl3HufLDMKzFKJFeNdyC3LlDswy1e_-M6KCRb8FmOWbMES3CBFAp9KRdQeltR9nN7siV8HGssuKBYyeorcyokQR8oF20mb5H0dB8RXQXcQprPk9-Uu24MZivnpep_tqu7eUJuq3ycQgw8i1ebseIxICfMfbOSWWh1_mZlAXM4go-DWLgCDGBmJ-_REQsv0tfktSpcXCC49G8oVcTA4MOpyjUX46ExgGa1wtY125mIZ8kZOrEipel79bDkmxUgTFT7S0MixOPpqybtZkxPtjEM_3gcYJD6YGRc8Vr3vLhNnmfVi4GTNzzfALkYKUS-V-h6NLhYsLZC_ioQmqYHrXojkIiwTOo0bjt5RAozPtiHygaf-w2LVTfwnEUJEH6EGMTUdlx7F9UHkUb74w7XQdFtmBr7FPwZVGVIee8uNGm1LC6uJmY2PBaKtaXhK81qmd7tjiyg7o669sJfj2tLrqlVgY4KPa10PqtOaQR86XJvwccbC9ojBkx3rLQJ0_ffy32nvxohwm0qXZyeKX9a6AqBwA8XDxHWRri98G4P14zjE6fO4Ozg-8wBJ0HJaChiOQ33Am9M8ZkLc_VImiq0wBtQ_AkT3jrEVYHdeVxmvWPaU8HatAXoy1ds11HIGagnqe0GrEwUQ4U3agh69ARg_7zSp9LDBLai-XYn_tBau9biB8BCk6FxLtcadG1uRTfQIalTUtQx-7KEdP12PSvsAykmyialEMOdrPt_Qckhf-YTvEx4Trs_UQrbR9u2--AhwQyGi3Rg4CzpPn00soL8M2BWZhBLm9R7taVvS2assqKxImiBy1-tzzJBmrDnMl-RK3AbhLuTqz8KWwIAGqlXcdvoD6s2xjqquqLGv6CMRl7ZIb6MQg9L6PKqXZXonqEwC04s5NE6aMw6LnUyKaB9lqvvOfLumV3uHQ9Xw0jGrxmosgZaiN2vIiSa8Klb-ElkonCTw1gtwTi17BrkQryq2XT5x501_zAAIK7GSbth0T-pxZu_vtAvwG9812_0vFBPHoNVOWg5IJ4l9QnppCunegz63l7RERYtTYXdWswsovogvNp0BG24A6EaX0j7zXSd_dNgCLH3d9c3gcQ_qiYZrIUnJDfJOZP14rUTV238fmgwfcvYcl3ExH2DA3E4W4qLA91JJAHllZ2foCOW18jWHVhZz7bV82x5h69SJlxr1sQFmruqZjDEDwrWA8gLrBFMFpTgr2HUhrQ_qwFhBLgnKLaIIWKnXsKeSOufjcByk7fHPA-3yGP4u04o1gD3gkNF0y9a33l4f8jAtPTGmqyw6T_qQVBQe7Il7-vTZL8ms_6h1-dPrd6HZEGtSMwaa7Kt41xyq8kkKGFLjVb_TwH83-o1l3GLP6HAeRsFa7nwcHL0Mp4ou7C7N-0pQvhEYvRZKMnk26a0p8tIMmQxoW7X9F21g5-ycEE3rUU0vwwpGbYuNna25RrOx6oPhzo5rFdbs2sOkdyXPVSw3zOOcEoTSs1pifK9840xdQZOM6j_gw2uPyjLJ3QbGNnMYXmK1orqitC7y_M_qhzI2bslJX4bOEYFD9HrV_AfbADf5-965r_Z--kxIm2GTu9JR8eexai4Vs8cl0QkrJv0KRwUcMIRfPL9_29PfI4WBFZ30STvwtXRjHtQQWbHOUVX07bv80aqwejeviG-8dVhX7FOgipAIRDx3dZyz-DX3iGt35XiDNyHSs3GFXP56ri0Aei3lIC_j_xrqu20bruBg4-vqetH3-Vrel-XY6Omh-F9RViftV7SZPXdT75zwM-5bxn3OpvxJO3hynVVGFCjVH8-6rUrgxQUOW46FHkmGJlkwynWBKgAvk04KGz72AiLXLRsy0kxLE8spCIrrqEUtlOLD7cWyIidsSr8_eDCKNdPnAEoZi1-h5dAHsnv-p3KqSML_3Kp1gueucHeF24uvCWIkTbfXSix-wv6YvR3gt848n48x1ktIBowAi0H5P01K7_uW1Dssl8-yGqTvhzN-zRwkgjD97h-j-hiSY8BwrGPuh4GO-crUyVcE0jtPcgECTyx7h6quuZMq4SXC_mQ4qaCAsO4iEv23GZLaUUrUsnbtNuGxBwq-ZWSXzkK4HVU4nZXGVMpKBYjumK5X7EXbp4Ez5XD2OHTy63dGMhY3hgsfMA4VSnESzXBnSZiiho8pVPhIJ8rzZK9ZEsakmSKNC_-3bU_sMfBdoLBGUIkQbvn7dJawjODzioKOZoSdsSAYJjZkDPwcgDl6zpfYnu1stqnhwJsnc7BxpWHOvcFwqIkpp7QmcZs9jSH_lO9Z5MyBvFBv59_aPkD7uYSJuIsvk8SqS9LxTmdBBcdto69bCV5gMPWrby0G8cr0zsHSagyjq5X6K1n-wEhn4rAYpIxiNrugwv4Qj7hmXrBx14lHeF5VjO7nB9h7jZu30J44mpuwYiHDyVDMgPm0x32Iuf9Hrup14I61ydd_FHjyv-B1pqjnZniVtsFpyDXlU2016WaDQHQz6hKjK6vU7G0TNdEIqCMa4MWer0CFfn3f7XtNp38Hqcu0Okmzfk9tqE5k7QSHKIRmZoCKkhYLRvEC-IQJKx00SaFPTsbnt_prmMu6Ctv5GCZpgT8xsMO-Ot_Q9Wfi0ij03fPJhWA1TDqXeou0xhZzf7zAfM0m7fk893TaN9dAZ_kfzcZbVgI9ndcfWUYZgZ5yOOynonyB4_adF14lAtN-RpEyoF4mhssJ0unm8agkXyo20pSrRto9ScmCYkxvgSE7BkihpzoBcC6brRqUY2RD-frzQbQYUMVgYTLX4nkb595Guz3K41yd4uahhdfKoKbPPwa1JyKpk5hBhDd6lPAUhERQopWPpECMup7MJSa1edCnQLk-s1qJqKXQ-gFaTnRjHb0xyWunxNf-JHWRDO8NNH50FEAldrHTQDCvqKbwQ2gT2GpZ2B1xv6JyiP8sdXGLXRLQqPMtTna9vMha2P7bDZ0yQI3k5kqR9LhCR-NGCopsymRC4WV8XZeh5FurRMVxYTH9S0TBFdy_Www3VsfMdNBOo2Gu6oPhJP5Gt9jAPq4fdaC89zdWbSaJE4b-kx3WFFOhLQ3U9CZkveWGWQegN_N5ZbsERjz665yvg67MYD7eDR7M_rU9Bma3PKCCDBEyBEZVdmHlKSTTzE2Wn5LElFi3cGb8U-jbOjUtipVUyiUK8wIqQgs2qN9dvOGCnfcUXiHtq8wqOLLqL5AF_MV0y1iQkQUbHqMut4_n7-comaQ8GPmdfd1d9BiJPECGt7lcpOVOPBnIido7es-mQ-E4ibkVRA97or5eECScpOa-pYo8iucoQKkABIcdhtIiJty62L_Giig2e1M-WvAwf35mchX-CoIjToJO9d74FoaoiouBSysypjBw2jAnEtqITFxngtbqE039W4mnMnIYpZ7sPP9bqdhZY9yyrYoWHyuox8bcnHvjkepHse7F7jUqPYoigFx5AP4d7Ad4WFp1F4pICtCb1AGvlhU1aGTYcvipgDGWZ90eJG5LCRfJtGjuL7MTK-nVXR_0Oby1kyER2wdXgjbVTxqXh_9gf5wdqW2RCIW-ZGBZZe4cBRTrAY_9ff2Pe5-ukmrGBS49-lwP36uzKlvh79OTG3dylKjAhe38h3utKK_MX6wD1_Jmhxjrsv9AJsCPKP_0UQ1423XPMPxC0Y6FapobuvMvsUBs2VyA9fQ-C19nT5p-BWsvdB3fOodWd3NTylplyN8t4qn5fF6J-l9DvEcEiLT9I-vtZl8TDWBQHWDGBXrbF1jabh9P0WnHEoCwmINcZK_Udi-cHunJHFBN9MM0p9dHFL0hQWb45Pu07sQ46Qq6UQJ-_B8cEdtZRh8a-I29vwuFo6rud6BO5pAJSY1M-FMm-uIeU8YkcoIOqk30HcNehtURGhV6stQdri56UeRCBKRxacpzsaLGVwohtz1rMjQ70Ur1vg_Z-pYq_gREdKQdCl8lK2oUK1Y3BDKItgh-Em4pcGxGnHjUpDLsiL8SLsODLIBXJeS0SFdl3gAdWNQJXjA2ObK7zx1gK1o8ry9Pl1mpUCzeKDEGhnGBCGMlgw9Ceu0HU_ppuklw-vjSfwxTLd4iK7-MOKAxhqMHykm0ZWg6IsUC0wbQTzcq8qcWUT_SombbAfUZayumIXBIKu1FSj_GAXbK0jdQ60CQvthFrq9PyIMRVn7__30p9Fb770SnGWd9wlAqAwAN8GElvi9jegU53NFF01-M4AaIodV6dBaUCrq4outlzCuR82xqVZpAfOonDij7QGHwQBLPwuXv3vaAQkLw2Azb74XZWDOkYeUxkmALYVgBvLBq8K44x-4UP_YVA40ndHJS1aNv9rxzOssTxQJgwzy9qCMYhiCXkgFS_hBZaSvolF7PY6vzHpG6jSX1SWghvVGwDIOL3esgVp0cVK2jnTw3u-2qR47HoZXcDn-eliSAucwqbfEDT2HWWMe0mDCWd9NnNEij7cndsA6sFIwrMlFtqHUe4dKzELns1vnT88K4l0jL0lcCBmDwsYNCVxwF1JMYQb3H9jxtcEbrHgyNdzFod2mNHwU_lFa81eCVXdSXdWUJHnANARtZ9EVnlzy_ujWh_BBvGsSEIgpYrXHwznDhKn61qTS2VJ7OQe8N-Oy2P54xl4MJbTViQBxniQi5jq5R6yfaX3xfRegnGKTjuL0c-mIVEaVAJaWJjJfLxlgZnaQ4ft6zfozFKu7yDUPSAHO7qk3fMkPceHfUa1Z7ATEP4OL-SPVLV6XaO0dgM6Wq4kDX9Hg4EGzsX1mqnoOZQkecfjKs-Vckttg-E3zrRFhTZHF0M_JNF-1WvKdoHWh58ZxxfGW2T--ChzxEUaDpR9xtm7ss_kpGW4dTsEvvwwARI5MiL_KTyqAfyRWe-1dKAmtMiU-CrIt16zRMYYApT4qFjVWgbPyEOMIGrWrRaRxA3HkxEpw9wW4XTionIVXX3B0o8Bo-TfRXa1fHan_tv7Hf2nVxqjMrNhaNdcpIDA2Ce7lEBYZm_TA6SnOnygXyzDRT9F20-f-5CXIqcArhODTnFcsGMcJV_5nCn7TTUlFnIBny0CwAJzI4FU4d2Dc6GIm0TU-ghAoR_L0RoaOZWKOpGZ3mGIcNSxb9OBhVzOxI--2TV5tHRYjryA_NR3mWQ9HzHNKKTor5qbDd5d-jr_KFXvRCbAUtCKi5FztWCUssO7t9y6UntE6rVbyPmdOrkatzzojbvxGbgQ2p3hLuh_eRB3OZgOJFLdrwgaBOHK6PqGMxfTM42yqZ8y78ygTdycibw-UJw20UujdOEvWciMU9BI_N87Kou4Lmjv6m8lMPX5MEA5N9_LMOS42xHT6rH0re98GnbPdqmuy3p6iQ7aSjwkXA0ppl2GOnZpWF6g7PvKH17mVQNhdaKy9pXgVRCtgN24p5RqXNz7EZ9UwwD5LBClvrwy4edCqRNposrIGWFEh_72_nZiyFCJ1qOA7hANMcwy4YbAZLcZWMVwZ1IIz-1G2WXSH238q8H-5sOSao79J1D_CT7AoVX460TrdxIJtHKQJzVe3I79m3AOT3Nels-y1usgFAWFFmwFuOqBwtQpIsI5ZwliXyVwcZxrlKHNGF30VDiTGtR64FXUZb3Bq1eEUvy3Ig9N7iHyFxub4gKMCDkCvcq6QSxu7rptEBOY9PC-pdb4YZcPsoVXZxHl7dnwUDMuBV2rrPu95TeoHe3ytRGyNzws6M18Lx5yNFkwVBJMCAXBGcdZWQWG6ORlfOjh1-qvARuLFjdjz_1cURcfnRBSx6f_-ffEBYheuD7NlBgNtra3sx14Ut-q86UF8q-t__eFKXwO4dzn7O1UU_x0jR46r_0oyYOwnB_TIXu_LNnKVMNaea_KHw2jtWyjtLSsX5GzglZzGughLef6X9okc3I28xqbVX3qxhNKaBz2vm6udwxRdkG7gCM67nRFir2KdEy7LKjfRBiBAIJj2ebbgkLl55_ebt50J9Wr6sqwzTBDyopPQMAgvyBcH8UxQF25DqwapGq6EZd3e9bx6SldzI7owRn_bEdpBU0K7XLJMA2nDKAF0VzKku7zPztbid5MAn9p9SBCJU6P5EX8nmG4VMYsk-61QCYKmIhH6YtQqHUIUpbrUrlLmQuHoDd2Kr9cZ65jKajGmSQBGreTCrp0bc0M87zX_jS55aXETD5rB3lnkFpRqmNEEifDEhx-lFD3UJXdkdA7fyve4b23utggPm1sccvqWkGqjBylz9qL-jQVlTEvsueA9sbkW8jBc2nOySlrRoKibhMXLAQzyu8A4bGuK3lrLiLtQ9jSsiO3qSX_IPZe-MtCyFh35Xl9jsuetBlV8XdaE4oSL6f7puigBe9pEcf6ZbkAXVf4r2HtMEPgjhY4aYiGGbx_c78bXxXSgul2bK15lnW4F5j_FjYB9RKxhiWZtvBMm5oC18EFkWRVKg4PjFQ_JHZ52K8ZU4jim2_iYLePHarP449YI53OhiHjWz8oRiRdK-93e8-GUZrk5tH0GlzAZQPfZo1GwzhBWWDvtqExkQAIcPB5vx46Dfh3i4dkWrRXJwVq6GW30dl0FrOqc6Z0z1rvhlZG-YLveQT4qH7m1-qGI2xUNiWuTvdqJKs88XPkIDe1W8P-cNnPLn974Etqtzx-AmOtyZlnlZ24IAwS1myxJ6f2h17T36zcTksNL7b26PKuIgnz_vZcRIpI5_0iDiScWQExbn8UlC-vLS3l5CKli-SthAKWDcPplusdWwTSyTHtSHOfizAkQPNUR0uXr-Gz0d_GKA3Grj8DkUEpTnA4doWBd23bX0_GHogtzXCL9oLaWMmGF_BPPR04fgn2CHPITcVsx_ZyNrVlNRNWY5NHkaJygb17LDt6VHK1DL6uxk1rrMMpymB_xcCUBDFzMi7b-aYY4y3Y2f--hHefGTm6mnp1P7zroJwbtFmX_7Dae7yST4tgnlxNT-jE6mp1YL4Zq-_xuqiS97nwtw_CbrxBSFci0aUxJQY5PXpDPdzBYPiu9LGd-adg_y2E_NKMmKUUKfLyPOuqLzcvoNBT46W947AATz6iFKZRUumidBD3vB33DixOMEoy7As49T-9Tlf6jqQFH_B8x89OqcxiAn19NGiu8JHll_LyqAsimP7vGiH7Czjzs-nxI9_nh6wnmLTr_ALXOjJNkOeMZ5-fpgCqMGJJ2jrhWNj0X95bwakZ65z_wSFPgjU1S4V9osavzuqd_1nHWBBqMSGvdUK1XKASnNreBgceE3pXys0rsnKscnOTBpO9A0BgsHj9lFGBbTR-w7apETO_EHODbE7AiIxkxalIqn6d2ZVBmpphTXVlIC3Q6_WlEaY6v2hc_onO353qGPpto2Sqc2LdEeI6z_gRupw0NukMW_H8Rt3JMowNZYOZJYrby5Sc1aT9_y5QVGDTtOIoHmOktW-TU-1KlouOzSy7-6JFYzeZ0Z1zXkVxmOp-82qg3b1I8NDUYCuJn0lzoGMiYYe70s8mpD2z3DAHzOfrnI_qX2Y8pMneO_xFVodjK7_cUwnr8MwxgFlvk5qEIqpVQY0EYWVm0koHFy3B8Q_YV3GkoK_uSkQiAPHX2CrxLZwc5aQoD_pHAdm4C8ORMlk0wAJCljr3slW_bU1KrCPhr1_gKsmm9qjfFEY1SZHhTmuN4_sUUTIFCwHuHanWIYodCXqxZvI3qA2cTlKnDndwWNeacN1JiNsbk1NC--22RFqrJaZhTH2IIv2uVx80hM2fyJCfmO0ojbjADV1SuAi4RJZyfF7GwXKtEkIMmmvETpHKanDKJi_wARKnuYJ7xrjckZjhQeELolT3DdJCb_ZKwmj_o_8eEP_6r_JDNFg02OND7G3U4iAnCudFNV0SBAKY5xmyVEyRB2Psn1YanXeV_KOV1keqYhv5FGj-gUEnt94ORihJli1z4Ge3wkPMhZ2fNoZIPY6evQvG-VnkfWwmtPC5Rh0zO4D3aYUc4R6qfQfzchobgaHq9TurLx0cokQRPBP-MUTBhMFjVPHeyU3iHYhXXoxbDK7EswU6wsyCRp3WY3KpHn430XPRMbUTvuma1zX3FmkDoi3k_gOhbbY0mEgg35AHA51fF5TlevqHD31Kt72t1nLccZySzu2I5VyiZDBElS1nlepD183PWEgZluu_wf4TkGQ295cgZ_KICaheJjnr9vuvmoeTAdF3wS_SZYauu9IOOgVDHaesM3NApMI75LZltokA9a2eHOX7xwuvfseMBD2RUP3nPBlWRlFIzYC0KsXZRQRZYY5n27-s5HWu2_NJ1aBmo4yaozDvQ5gh_482rTbOh3gVem82iMEI3X2hEuiyAl-izmPcUwWgqHyhWom2D3imwglz-UrQIaTFsI6_bg53k5pa-_H57PQaDyi7E4pildWFOahRGQw200Em0LrVC1Efu4sesBeWCc-DWp9jjwlbWRfF_MI2C2KRzJGhwVl1u80uCLtHTov0mvv7cUUFWFM9e7UIOU0V1S-cH_n7gazKBUOU72y44lexuAFy-ejLK53TZXMaddQ86I12UN6F49aRmqWWZeaGg2QfnY_jSgU8joG7ZxqGm97_l7jcJV1pJJqkbbfeZ6a-t1N0Yuk5nDVyE9Z8M4M7BF48ZlBq92fvnj8OpMjVQzS0pICOwyaefJP6ByMxzVUmBs7splrNN4b_PxCRAyfRKE3vCXcapJg64EACTpraam23holZ28PUJT9dn7NaV3CvgBBJ8eiEND5B0Y5a9WU7nWoAAXexq3uJPwC4cEgfbtBERSJynTw7b0cRLW3M_-I4OdMlO5sumsuNYbJjGwjW8xeWQxsS-P7MROD0qD3nEXshPdwVRikhHvT-ZBxzMl2iZ82eVs7K7XdfXpDXHjOVALsxxYwLArNtK3T-tzKxZk3gUDHyU8BkH06xlIQU6t6GRhL4CUUWnYQNp6fXJvBwYijlCyRncXjxk6Y2N1RZCrIiz6vrGsUqXbnDpH506Nljc_L18AtKhNiAdq_q3VsdRbAX9ILvRgZI6FCZ2Iy5ucUUf8BhcGlsQPY9_SaxWQGz5uImjwiopodeWSdZGyqiRzMPPn4npP_-5Zl_bHf4A-WQeXYB-On7kDQ5pKIsMj-UG2bIqCBzBOzo7rhxkpMn1yEmlVk-nP2PxB5skmAFmCQL-WmJQ5IyCQmzUXPvHRLRq1OpAsI0nHTWQtodwEkH0mrrmfTDHpGjH6aHneNF5p5TVTK_cb_emuisDYsnZn499KCzHvOCv5sm1dIKR8iiGsV2H1qN4nOB65EHQJo-EwSNDpM0hBdx_EHtfXNF8USvVzF7trNgLUsvlOpFXDvCzcZD_KGPku4ZEzMpOB_MI3rjToWGTJa1IfoIMVKr_4qd-Hq6Aj1RYByTgDz5k35e9L-nkig2ur-2i2Wtkz20p7ac-hTiWYXddz8kWdo7sqdUqYEWy38hhmppgSxa727REpgRJok5qhw6FCy2yO7QpXGe8HMkhqeKY8ifVRdIB-kCCWybci2eGadGAgE8LAUsYXY1cxO3C6HWdScjCGmGeNKV1PhN1Gn-xAZ_GRgOJwpRk5HpHO0h6kCxlrVjPUrl2-unHMP7VpUHIVCFOcVzKxCvxe2VdPdPGdvTfiXBeWCmdjCJeqESB2cv6W0mER6ah7peQsTzJ2xSbhbHhasHCk3RW0kuz8Y1GHUpzi0fjLDL_2V8wQquWU266Cec8Umht5mnu24wxN6wMjKebZGR4HlRDoL0QAnaB1p9tc8_VJPuPRFQ5ejhKmAa4r-TstKue7OdaQjfsXZVxMt7octLCoheJgWQdG41XCeyCkrjsuYs2ii8FiViCGM0VlLiJa6KeTzMggci804s12qOgrJZ06E2dCCMIqRWE_ZQpAuwvX22yLb1Me0ktMOldGBoD55EzVcSq1uRyUzi_aNhL2DcnxEKjTmt6VhmnoVdYNT94RGmIZkpOlG2697pn-hSHQl2UBFzOTHN2qIuvXRAQ3zBF-nr18v0biBKLf95_f9nYqP6af2JNCUOAWP37vnSSdtRfzlklm76bagL9oYMdfLy88oM1F_m5cE1GLEnh6epUsg2m3UY_ijqvzdGkf6WZZFvC_6S5yfAtWUIL40XD1t78D0NzZ3Y_vF7vT09huGoXStPe_gj7NNi598Xk1GoCE9tP9ekEFgfo1-YiNEOGw0vs9KQi3u9763FTmmnbQ32GAHCxCWtSnuR1zkAoqgUjjgnb0JicZaAFCdeXWIGB5sjzeU5dJJirJ2rze3uDzfT82tuFKsInKl6nBIJZeElD7u6klNiTKow8a4xHOC_2OEvMfueYPHA9Pc-39RogczRWGyG5ctlBf3Z5ljmyEmMNhgwuI7-tQMuKQwZmwlYxdvqVBRMBSPM4YazaySN2rZB1b-RYARSdvFLxbpz2xA1XACHdUBzNGI_0TCqQnRW8B4UbLzjbYXkIaXyuKEHVlZVs7jKFvZTE8W0Cup9_heN4LHn_U18L5v_gn_wOk8WkhoJ5B-E9BXs9Srr_XRXhL8uLNn25lZnUloyy2ZOz-Gap9VkUSfNfsjDeTdtJSoVCO0F9k9WP6JZ1m69cVn_BImjbsWgFG81c5wpYHrff7HALBE0PQrofCMQGFH6JL417T-owEOPx5aSJ-s6VdHoSETH8aM2tEsAfmZunZffEDaQTLxzCRX1cJTLFuph4rmbHI3iiRnadhk8SrWfSMUHK_SG1w98JPzCLufgJb6G7TDCmhvc3Fs3DZTEFpf-gc83y_qZFOZV-gv3xgEhwLxjL63cXDuZx3pzRWR2Rh3LRRPrA1EKYK73AzOj7bpRgPfLBFiXxEUoQNn3FMpD_lUiavx5S2Au_QPellBBJCukPcLObKblOYe5LCyoAU-h_SiJnOH5Mn_Ld7XuwVhr_HVETPNAK7nmGbO7oRkiBkpg4rV9xxRifhEODNsmjT83L-aiB43SOB32xrKm5egoDhYu6SwHOBIQyndWkvd4ggbAUwakGgkIZcN08YGbc2Sokf9ZbXj3VhmZoeyGu6J-yzb-stT6QASdXxTMfiGAy6gmZ32K584Vvb3IUopNl93m6cBosofyN0oX9Xl4SFEE8H69bShsqPapKbamySMOJzY2EhXRC0FwkJKyAsTaL5sCBdY2sriYKQQ6_hkFNm-XwnEj1jHDAjM9mtoJngeOH2HOf2l5j85VYmqfSZohRo_AqkpyGBH9Ypad25h1mgWBifKpvSWgeTXvkFEs9__SCa0SVG1IXLNIFHMYM_b0kItiAMv97w8xEh7KO7UgwuvtJYth8fca7Clrc8hk7RW_jYXxkL6c94QnkTvJHVevinNXuvGGrJNJ41UUwcCJSUN-MrAM1b3Djc8mMdWbYUn4LLhbsItJFMRrFUyuvYbPUNuMSLeJefywdFF4sheVutm_o-0bYHxS9Zd3zE20mCBlC9SJNiAqjjtsnGMzwmIqqwkFb1IlBP1d5qLyG4ZOLVkcx27nCTcynO9scIbDBknnSNN7aOBDM9asto-Pwf4oNyeF3UM86OlY9DaSxNSSme9zz8htVBjkLcEaQ6S4FDN0kFR0rAYx3TSDtZnwCCajum9PQcFjNvwx05h6WpbEtNW8-U45kZhKCj1TJQ0LZgK0kAClbMDrRNth6sg4So7PeHuEhbiz00TpLDsp2SD5Ye5tztaDt0VYEBesOs2cPRNZaNxML8BOKZZ3j9ebIYC1JkQupYaivBcI_l8hbUKb5uytnP5RkDzfuf1EzXRP67Yn03Pcah0ZCrogaE5A3bphwZaXJKGL7Ts1x3nbmz9J5VHe1aKxw0hoGuwM8DPAU4bynAuOJxCqPmc_eaf10J0vYbHheNqFMa3NUrjTZ3GLNeYBYnmGToL26KH38SrEnq3w6azrPF5Y4PmoFWBFjkgot_UbiX4UnMbU0Kwqo-xm3WLrX0WkhLt0tkMUP4hZYu106L3wOD9oxVuy5SuVrxMtPxXH04DhMIp9Kii1zXOqUNBQ2UbDZhuPDeDYnV8vLwntGt7QhL1-pknWgLCGj1lfJKI6pxrXE3nVbWBH6w9zclF1w6umd0l9TAvyrPWyikcY78hcUWFI3QKxiHoSyZEm-hdU-Vg-3jFdZ-EAT1phnfcPYS8h2CKe46YY_diBJMbQZomFI9oy1EwDdtYOvyJepeEb54HvKiaYODz_TWiEfA64-pu18GQ-33VLy_lWvTsoiAJX2mXP37duv0p_YXd08vnnzO2RzKnSU75Gc2BEfKkQ4YUjtTBkAh809jjTFB45MJDpu2sXVEptnwaQpesrLxeRjcid1atKGiGHcxA3OA8otclu5HXKSr8PsQVzp2oNOM3OdFfQ9RCAU2Uo__WXHa-nxLMVlIG-VSAaJp6hzGnn5pKRsxcGGqUf2_C0CWIhwkKCRT8T7dWtus6VOvL0NNQd3yuct82rELZKRwY11gGAMVW5VdZkcFJXIWzOoywERKu4hMz2AdqTNEDwg1pi8I-HtfarUK3n9nQRDXrcUsqx51qJ3V3Rrr_OyHSlZzUWb7LQo_ifW8IgW3olHi8cdxzjsVFpYpslT-jO-qrh-3OF-VgCzPzd_60bKKVYjI0sOnnN7mRh9uu9rsfH3o1yFkie5YO9ZHoUnTSyra6hPWW81qwqQDY_YM8W3mamipjM4MA4wury9a2_xxtg-AaFELf37w2-TFNFP73aznlb6TO-W21pr4uL3Y1vXysOM9iYcd20MQtZ8n_thDn1CE1Kn0_cXopIpOQdcE0rbgUwWHoCohSjahoVozZrzy8DpdWRWOgdIaIE-2isjENophEI0G4BLjnMgjGKBKkPhgPB1I5z5ZMhbKFxNSt3eAJIllSCLCGbQchPIkCOhtqW8PtHFluEH-WsykVuwS0V7pnn_IGGgDvCWfOiY1oO_C9KBSwZR6cqRaR1uBZrX1_Okod9Lp7p6egCEfTeqLx4s6heIKwU54DQwsdzItKRkq-5zBfTTun1X79ViT8IJdZhKOcjtNDH-fZGii1XmC_wDXjTj9hFpkCnvOBAuyILHcibXl1VERltrzBihTbj9X6FEAG8iTmg5cbuMCCO1Hfd0h7L_aU2QB9p6FtI0AD06XSUsbQarnxj1zijFiPJU91GWB7blTdF5iZKK4wwCbuAfNXSrxDayDRFTTzYWzOjvb6DW-R6EVR9DYsTz0HkHnNUNP3hb4-59JZBs8uAwz8goMCzBJUoeL08IjO7CO4oIKiPShmwnTECaYbtEnr93SD0FAKpchbNENjpe7dF-Wx_8dU31xPSFvv13_Cxxze8bdGPm7oZrUoO3TVLcCtAsAyyH_bKM3ZV7NPO8Syrrj4fPUdWzXobbQRobxu_1EuyQnRE8z3TJDt1zV9qX4xuuZs5HG8ADjIGrkMrAeuo_RBzRPC79DbvESy9q0iyait-UbWjgWy5dmh-IYfMB46ugMupynfz-3aM3N1zQg7Na4rdKEzfVhVGRZ4Eumszlikta85n_DvdJpjud7ZtlVkLglS6-hcKXvDlAs1rSi31pvHzea-xQoKn2WlWxyupVzmyjU-KgM911i0_J1wY00KiR2PqsTTeOg-0GdrvKG21E6MGenOvlix5W0LpfeaaswPKZ2beOnovMjtDTrtoPdv8YG5JIg1bdFuU3BufJ8IT4rLtmNsA1gItG6YQa-nD0HyEfdLimTFytelKa8UOIUaQuCOfxSIjG3gTTaYRD2KZAAnCH6xHGLTWJ4qQMGDDLbOoyYC_GOOx7X85JIQ2ysJIrWHqgb6NiJ--miIFjaPZqlwPkXOVh9-dL6SmBdLkJecjHKYWyHVl3Iji5qeTm7_y5y6BuAFnSBZ_JGBS1zQyys38ZgTdDya7fGnCrlsYWHCuCl4Kzvwn0Xfy3iRlxzgRC1Cn6_Ybs78Kl46t2fwypWW5G0ImRB40wc3ccp4mpchGmLg8QfFXGXnsqZEjFo7eqljSiVMnQEVuIAqhvf5uuw2J0yz_6PUnkVc_mQ65RbaDqdT9mvR6rBQcUlgjLUYGZgEhqmekACxowowsMTJOPA3SHW-75PyHuu3gcsAUhB8cYoKfkGd0tEjffJti-zANiov7Rw8uOnHoop-Pd836kexy0-K-wquQNCLKl0YmZu5L8gXTjSLAyMsmNrtHbnKfxwJaKx7xUYBRjVm5PYwRwksQNkHPAY04MJ9CQryhqeIzqhEm5D2u5I5SSiIiZHR8PLgJ2rZm2gUp0cbk-L_kx3jyrLOhwweksWg_92Qx3rPHTnHeXaJEXpHXoZeVP1T4hyobD4O3VAQ2-jsLKBHtzGp3fmZZnt6hG_U1TnryAtWIrMXqr9peqIjFrZmMXhUKV5JvhnZasseNhy7mTyNdpaCFqcqEBOM7fRT3JCJglPm5eHc3fJRLKDDJtZokvGEvIglsVMxPSARqxWQmCmctahfNjb2T429R3nqeymu2YrGQDQ1CptPLMCImEyUuRa4XYL2ulx9y7ed0YoYvoX5Neyu9g2hzJImoFc2gwTOAfMfwbVV8nunti28t6e323-HMWs9uDbVCZC-EJTw3iuANaWxf4IhM_8oeYThKvi7u89vRIowKvU8yVuNHOcH7Snobm6mkpW3ETYsjbR-WicJh99HsflR5ZnitgbqwXSw1PiptZlFa2Ry0qDh7MqtChdbJSKQ7pvueZY5lNorPmiSeKLdg2geUHrYIR64OZBRmJhjVIpsaJ966Bp0OBDEuI0gfYYPasHv2oPbMEW9yWHF_rDBWOL2IaK4Nniqo_XkBKRE31zVc8E-rYqb8iwMSVgOvBE0-GXMjBxjii6dGcLEuOcHf1Q0vTRsuDK2_NHvBUV8utm8CdiIfYA_D1dtlSodySjjAz6MhARPGGzMbMKw6yHjebWRLv_jyuf7IQO3Cd0qOoykioyWRwl_dWdsxlUmZhqXacNMWINeP-xSIWzuYJDbxdIhnAPNsJXq77B85jmcS5ZuGgXIj94twdRwhqKNKVE-IoOBWP-W1UvIdy3rU2-UPEvqZ8QG-S1Z25VM7RWaoHoI90n8kvIsfTVgOfY9ax5IqDOzBYfQiv92gFAQaJaE2QF6eHJehwWy57kD1qdlMHhy-pbkN1lTWefebz-L-o6zE4ABnSKBGDo21j9giX7tSf4gejZzkJxY3tclWODXDoejIBjSErcjdkYjtJrWqmYY-TQh10teZrp1Sgv6TtbC9LtG1BrLfCluklf_ra7Q99Pc95v4LwlGwPvgdxwbdIqh7zKbZa9TlAhchh3emaNX_cBWHzN8OqxcI5oqJnFqJuKr8iaguTznQOBhwXzXn-7l1ZnpOohawPEJnFka1ZNrjyzW1bOFHeT1szSsdMVBkqiNM7T-L8sxDgMIOU8Imy_e50FdHL-4uJzMAmj-7d87979xv4CfMOhK05bkIauDLeAkMJuf2w8nG6oivtbmYThVPdOs8r6j9qJbLX5WkYRGs2QDIwdd6ET1XNNg4ktkfMxFq6B_4n1_ld6uS0Q-J0NbIxQiLWyRLVF3NsNi7nU4YH0Nynd16csxPuxR6nPN8UjI4VLXqPzi8ztV9Mx3oZzL2f4yNRDy32sCkgt3WTLixoN1FdURU1-PUmqfJOVsd9qwNP6PKycgJcvq-dOom9HRlYttSrEYXHlg6YTwjkKiWRzcjSzEixrN5jMm46vlYBDaCEysLgEMKVdeCl-hd1RSJnXL6DMrnvRB-nCyq1zTCFJD_uJjmZTTTqPZHekZrz1jS1EvGomRrnaF35PiD0Ji_gk75TEeRCL1C1oYiBzgbQ8xbfNQsKs8_GuMgnGwNsUncmdbNrbKXna4Mp7K75BERZZ5Dtm4Pde49SvuolURkdjNXMZAG8slaEcCAkKSF9T5QN40nCbLgLfaUQHYXucAkPwsoynTbUqbjjbcomNEyz4Q27w0rpjQCxvT4EfXmNN773yWqitb2JrrSQaIhMsChqACxqtWaC6zhW0GDn9p_o2o4XCu7gCgbiOT8_yl6YRPLrwscddqI-AgcwOEGdpiJFG-xtSTRkgBA4IMAs4-QVzlyRUQwzENIihGKBr4AlSm40nXC2IIaPIUluWYoywf2Oy_tfH1_7KjDJg2dGxSOJ_kPv2A5fwI80hkt940HNhaEu9sktzJJlZvsA4lnN10wkr3jyMv77Qq_qpvYwpW0DsC10uT6aUSUZEPeiwja9vYBod3dXPgpPb57oF0j9aHoCaQXlR0DGPqh0dmO8YGf7fKL6bO0Hjca4U3ud62gAK1N2t0znjNfxruA3OqgllIiPFPvGA7ueK7Fo1l_DZxeOpK0fx_vTtXBqcY1uf34mbUZJYd6juKHhdfMWVuYCP3uzhc0Mvqan4MiD-CSY8HFPa9G2Hfl_qey5RMl2VvjJP8OnI3MDZeJK9OEmMEZpZ6rKQX6Gbh2s-Qkcy6QSlBISasejy7urjE51nCezymiuQuDFwQv8AEXpF8oV8ER77RdEqyeDseJJf7OeN18zYFFdlFV_ttznuMtFygND2xDipZIUjiaiYu7pJM4xrt-3ZThe-RHfemWfmUh8uO6GL-vG95lAgmzHjMpVhA_HL4r8JLsDQbdjdYLy24AcBU--3kNksm4P_RTTezGsBkQWjEtGvtxtTSha8kuB8jFtEyp7_l_LbNxC5M1ApOtKUjKaVZexijueOOkWKkSE_rsazhZCTFuEtgRrE0c_TzC0875l1yWK5pdwjGhSJ8Yv44POpWTFwfQWuQJUfCKaZPlCtfVdpXdHOwitZvzQ7Dzlq5n53d7PKccQKDzQ66Qbc2LRLrdMjc5_qpv3Cw5sn0T4eA4I4id6xIHub7AjY9CmhbWL5e9VbabKvdC4POGC9XyTRgOhAbrRR3weIQukH-QE4igVXT99ekcMRuOgJny02dYwlmLlm3jwVMA1tYG-8RBnAUH7e_Duc04D1ylN0JAtFZOjYX-2lIrlKP-e-g-GqY8xjQImKh05nRZgfAdd_NV-xSDAGPvwpMPWg-slcI-lEmvmiBWdudRWiZvKC876YpOtksJuvQU-ddyCgZSqq0EY39UZhqlG816dUWv742SLU3XGw4Y4zWqsgOghhRAE0BW6G4E4tiC_oChWxW6zy2-kDxwwa1pM9UetJPqBfZPs01y2Rr1brIm8-wIqcls3Q8gtEcMc83BRNyVeHnipXEv0mZPEd51TnhSUuPm7cNx-n9HYI4eUtXWLOK9ZJwwz1WpW2t2KLae1YG7MTIIEedtGtWuEHaf7750tLTPPKhd0Fsx6r3tH-2tZ8O4IFfcOnm-hUjCTePuvkSzhNhXrjjiI2eZlDArNHuiCZS41vu5D85O2ZxNRcNFH40N0DdSOEhHbOcJC9mmL2_F3_yWvfLE49eX5lhiiianhR-ILG9BsUwkSf1hdJBfxptPdi_5PPCZHD6GXDqOGS0EEbKktZXN6Xl1vEeHBGfnb20sGfvH_btL4fMPu7C04Y2vMznhD7I-p6u24MfjDDIJlHJnDtl8hW6C6ez7I8UskdQE3UOrFNHxHdm6uzak0xks1ryobSDMIVtnCNzFtRVMI-PMYcn09VGaVZHBm7Rq36iTlFi6Heu6j_ms5x2UFFIOn3Rb95heDsWR24ndTAtTIGoeu1MSEaDmWimOpMy4p-PYS694JirsEignrqpN2penEh7Y5KVnOeuzeHtGJyh_Z5CRR-HylTg5-89HxnMkiJUQAmqFylQzCJTRgs6DpV335BphzmTlYTmJH8WCOYtry4wue8X3bJ6gp6mAkKvPHV8cLsghNOpS1vPkJFfdwQFwyYTU8NkgNQYH8RoUvGUskrt93vbX-7wBtX8NhyRJC0-JjwhlO-kA9kz97FSOixzfnkUR5Bslkgl9MoRX8FrA-uAxSnrIht2Wz1AloiW3kPQLH2yI6LgjVWFBOtRKTvlAegFsCzN5N57Jjh7eGOXqST3m9Bg9CZ3w6GwPyi_ECfoY8uwMr8NB85JW2ksfSLbFKCRtHFCGUmLKs81Z8wfveD94Pbb0AjriKlFCIH35eeAhAsH3UsBi24Pa6StKc2cHukcmZfTW53b11rNe2wnSxDwlKvvnLOvA4kgnxhAE3fckudjEQd7aRiKM0hTe6mYWssnvVOUDjBsLrQwW4_E_0lGO8L08Tjs2YXQ8rH9xba0Wouw7YpalLFjOHsRQ-Oypk4uErcA5nZHDV7a9zx5OzlXOLH35L03tsT4m1xQEglIKR1XjL-glqsV9-d6B299BZbVmGaSKm93FeSSwVk3MXDxC4vUWubFi9bix1hQZASGU1H3RzseupbMzFm5W_Z7PNENuLUBwzFOL_DC2mN1jyrt5vJ5n2uiMD7HqPJADnQZb752Vtuh2m-icrgXqybZRkPU_d9-EJBBipUlJnATNLiB0wbngcB5Bo5co_yYDGB-_TOAegLxIwltxcI9tCvYLiS8cree2pEVjW4seFActyF3WbgBNfKr-aA2wEhkOsk9yYTXvB-ZHMDUhoaxBakhnmteON2u3L-OCchxgAp5pqHcLUpRvnodBWbtJH1HLqibmuiOGFyLYZyzuJ4BRtx9hF6B6G2AE7dn1kM5QdeO_thKA0fZyAl25Lc744Q4EMzKQ8GZq7srLwNQB9BtiP_A-94BKXhGMoi1hhg8EwabGLAvFI9Or-rBKF6tl_IOxV-KCkDy5OirI7BbsC5UyP3XsTKQdhAW4amu0apByhiQ8TddTSMy46a9aGtgF2FXYWO0MNq8JBj1KHPxRhUc0Z3bqrGV3V0RaUExFZHQ9FsDXzZcTUVq0oxRpLJ7pkk_Mxi2gGPqWyrnFU-wDaE93Xue8oNa6w2pPR4sQ84aiWxSwFqYRu3YJMCIxBL9BAHrWnWRSALFhsJZ8y-r-d3NTOJ5czzkvHOMYW9yxc9iEPEmeHieXVpJ80c6Muh88Nf0ObYWB2cKaKqUTGd3TCdM8PB-IQauXN6iRth3O-uy5YQ6RkJ6vKNDAktMhE0IVD5g-cBwxgMgqEP6FTXbVVL8-d7-BHl-uhnXki-BM-mW3PK5AGlymlylvGj_otU7r4tO-L3tqCJLT1WK5Qj1BSXB08Jl8gI3i3pXsCbpjyLzbrmozns0bxFHvxO7SX2MkzeQOu4XdwnD8Dy6aROx6hbCc8bru86MTkSl4V6z6AUiV0QS51vvtewYtcsPWryO-szu-4g4Q60QKjHtTZAMCyfhSqOKEuX__56ApH7QuD06N-PuNGF5aHo-NXJ26TXm5hIVAPUZtLTPEd0u3NmP8F1mUc7vvzdQR99NwfjGLA7eNEYFUSd3GsdGoqV4kRqw2Pa-fbE9HORDdTQjvedetMAE-GsdvusOEeGvOvI-1UVKuWEFfoLgSeQP_FpV_9ZohQQSD_TwONGVHRcJkZIQM-xzRAJF-lTq12IoURk6gagu3giSUMuDy0rocr-8XZmTsALz-HQ2-4lvmHapgO382WE-j-6ehGOPwrVbExfrtz3QhUzR0oKxtKDCAr6zyZZfgpKotYBMs1SG21-N_Jsd6_wmDYrNZ7vMiTKIkidw4G4LVqLyA770aIR114nitkGVnP0NaPyf-2do-NowmWf_zV_wwFzn2fEmxBhgamVPagqJ4XombSjM52bt1pousxzOZCcPtQb03DnSRsiEtSZjvAozuk-TSX2T2L5xXp_EiNoqLSzWnJ6yJhDTZahdPKbvqxAisXeWWuLcqxuX8pKphmlOcLHcwUZkqQXdRGOg3JafFXFc-hg26cpDZhEK-ocyE2NZ4C0PUaGVNaVBlDeROGND6JRwbzdyQzt_fA6Zi9L8L4HCSWoMpA5pCieXj8j8LM8GAyspvwbOPTDixHwpzaw9uxh30Ca5LdWPXc8WsJAn0UFeFAKj7GXP5hjl3dsvdyznMtWY7bSLWEj6qWBWzoSw_4u-bQfhdR8SDi83KwDYYb_griQMUIiwePPlA-M-cyrdfIG65q_Q5vpVtZld0fgxi5Emw9M0NXM2vSF_FY5VX4ZUfbp8RYiLJ81Dw3HjG4LVn__yAmXM_MXSdQFlEp0IeucA3ecLI38xoJ0dF4S8dhN1nV15zKk0GQZ5zmirhyIORpU_9wnZNG3aQBgNafXtwAIGq8ELvLI_0xqvK2Hbu6iBdUZVIXNtx6rZrCYN__NauaZLl_N3r-VsTNlgh5s0ABklkDrwnSJOFxExRErd7xCzRu1bipv8zF7Pj5Nb-vOou4Wt0Fzflv0L1pU09GOhwTz9jTYPPDp5LFGAvKY65wkJ4DckxNatFOK2exvII8jP-QLE9R_ub_fsoSUlwMJ06CJHqw31bmV8NfDLB4rqh53DN2EJMgKkf0A4NJluVxV9E_vlR4nlqV18plxIeSBBrRZ5PtafRJFqE8s003Q-lquHsJbkauaDaM-qP0sj-tFTnhO6dc8lbG3CyO_-iPlrFMjTcpYhOt_k_GFNwAeHRosLWnBilceq7lXnbhjxg3y2lBhiEYk_DGmGa6yAkzAk1DYL_vkSjKUTjWialfDoxUMhK-lyIMnDbrEflCUiCDEFIOIX1RoVmnQw8VtMJJc-lm_02L6tk6KsJ8MOcSbEy6oizZ0NWnazmILhHTOEjMiMpW9KB6cooQS-fo0jR3TEAgbKLQOgOZ9gve8WJydnFgrXvzMSVicasIJ36gYLaPGRMBSMJYPcGm2hpA6Jykq_MVnmDLxRIQdkjvUNqFiz3Hryt7q_OeTXkxPi0FuDlPwgqnkPVMsPZ2XnPtNpSDDuWefGohE-SZ0ayJ9ST-S3Uns0Bhcdv_KR6CNakB6M5dbxKO9GNlOLIpvhfiAA7qVUa8QFOqeDYWkwxAIcoJMkf1FEAAdLJxOBooTg-blbsM7SvkUbv5JGzDrgN4OwS_EiIuzUY_w4fC07-L1rUgPMNbw_RP_KVViJ2FlUDtlXp3qA8YY_fXFCokiy8yeuW3IIsrADEfjAgaIwOcvh2p5TJafVk6L1YotFvgXNU7X8TqCU0rTLVnIk9ChwgC9TbhoLucakAfXCf2JUVaIbUotiaSL2GxKTOJ26Ph8US600bY3tKX0QufWxCTFtqFv2wX9YGqIjG4YLMHmBqQ9tFnL0T1AVqs0FZdELYDowkKl0rZcbe_qJrbIu5gvoFLn6LPAUGnW1Zou_5amSclnbSWaFJECNycCwoDZDsBrPiqAqKE-rlUe4znBHsZp49nCXdMaNPFORC70PIyCpTr1ZM07GuCY0ISxFTMqEiYGkMzovp1g87ik-zKHvSLVI8SBOS5ykqEIjadOu9upAIvIFvnBBTBsACVqG3xjhPXH_1AtIm3RGJVLDc5BqDGEQ4G8VIQ9FpqthmqLF-YOuHFlF42Z2J1K1YyT16Ht4r4UguVlex1mDSfBnFeRcuzV7iNEi5zyobJziMmL7YtJKZzmALPVt_KElkbubmzbSaPu1zVgvDViExxRAGwuPWFIq7qTvg3sZyKAULZtn2QFxvhmxTlW0O_ZkkNaGF2k0nkkwfpA2RnZPBykkGgcISEkJNYYol-feh6c1175jSJhLLUqnwtuTVkZiNpga8vIzBEZD908eRxRePF_M1EPdakgv72XB5Smh4rX2JJrUgLRRFlkmQbndmjU84PpuwSytCQQljxxtG71RtMxz3ep0JA1pM2ogITG42GBqhuXouV6Qyvrs__0NWX6kx56V0NtKV3crfKQh4HmscJPvfzB49CHiOJIJAQTNDytg1UiGbQ--E-s5FH-sJ-Yw-jodhTXMKANEtFvTLUIjluuM4Ph72eWETqmnd8Zgd4urTfD8lpbQ4o9_34qgw3h4YUZZIAiTA87hnOCM5noSHv_pKOpLL5gwXUdMd6Vx_tBBBhVEBivKsY1vUhp8mW8EYgTG9RqZyEOoEnP4YZmgQDlB384sayvIOhCNjtxhLYpu5zr6cYyhVY1_pq_HREqughtGmcV7j6Cz8T0VALQupGk1MPwtS5v2s45GkTCZlIU25SBwifYCj8ftA-YeHGcEvEye8jdD5H2TAUDdW0V7wxY8cpMAi6XzhfrG7dFNEndU_K-Hna-F5_uJzarRD-PjvZo3I2PuNTmpmjrElHGRMVvIvX7tzn26Ab02N-8gda_Pi4AM4dDrbYiSz4W0hPkPjK4GZmeyw2gY5ulAvjZ7ABVYItaCduDmy_qSQL7N9_e4lsgmgp8Gi1bQRK4qfY95ZyoflAEE-YZbrlr5rw7tn4t0RlrlBP-KbW_PoCQ6s1BTPbtZ5hHN54lra0YE2fWf2CEz5ueJX5F00zLzBalMm-j7EGIfy4608ntxiPYIJ9edQz6gStOUNGMZQ8cJz8i07M7v2NOfu1AZ0lgLAy4cyBa9YaKvYt05lzZ5A1PpY-SyF9Y65L3G9_BAb1ulmlPc8-3FiHNU1N2rpX2i5Ki8096oPuChm6f3_JW05NtTm5jAI5Cqs6zNd_ZgWF1AZ5gRx1ItZQyomt01w8TaSKrE8vKMIhBlOGniCppde4aUTYA9__jqo_uQEd2EBYThk36QQHFz2Y57XXNxznSaTMPzMNuuBxKzjWgnc_oKww9A9a8LjJ_acl0SasNsz2pQfc-FVMQ4cRiCFzhL6_43LAzRibCl0QAOZ9f5Y6g91QiAdzmoDzBpuRPUlVJi_BnUPBN7MTtiHADDM0qUTy9OjiKvtpPC2c7BxA9mzI4Z4NFTNPreZA5XOHuP7gmAFnFvbmHngADnXOoRvFyiReCQPzV5qN4ulK8lG5QQKj6k8sQuxzBdboGUzGdDgBFpugOACwe0zB85uHx9Vrh3X1K2KmnEag0qDt5DEMUIR2s_Pk2jo1izC5TrIKiqJoLgAkkmzEmFaDFBRdUZNcj_BU8-ATUxJTuxXH8hIZbqy7UbF2ya1wZPxx7VAWca6ZtGCFYPS_ntPDV-8NQRFuqOMluP6we9ETZFN7U6iHDIZt58ewcxBy_sIoNNGS4GFABN6FcYE8qQBmVIL8MGxwD9EPv94Aw70fe_HKZ4OM2Sq3caCc0CJVYgfwsZpG3U80r_mfvhCbQpYjyTysc5HpUsW_SXL2bcDhIXyoaSSyna6eDuLjL-PnIEJftdZBKQE0GTB0YZYEPFbfXSrw_fJiAsLwxPDHp6j7a1mT2x2TXvcjeSK6m6TUurnEEw3pdP_QWvSUhf-W6NdoW6MhfafU_bPs3NuMcsIxBNVjcrt3PxvKxrJuHIdgPdqizzcb_oA3EbQ_JQmcTKfLsqzDjHiYSMNNyry0vdBU-uYpnOftWZSDPC6KtX6J3F3Z_sHMjuIcFdYXmRukWdCMoFgW9WRzeHTe19uTUhDj6J2MUHL7XKHHCEMszJFeizD6Kb1xPv5LhMJRce4OKGPcgj8vqB64q9zardvkhB3lx1ZrbX_YoN9GLCw7sJr74x1UjWfiLN0IwTUKRML9d26clVeBFUnB85TvEQhX6kJwwNP48DAx0VomFyMawI9LXXnv46yaGGhYRMwq2umdSys2Xbg3ma30qf-P0rTIMBeYeKQ2qLbvLWdYQjG3qkUABcvfup9aeL7cuka0zKaJsKuKnCAkdP-Ng4-j0MDL2uO533uynnAI6wQSoHLceyMnjOOkZIpg2IUkQthh905sNteYBxljjxPbOhgtQDEUM5pHlB0dfqGmpkAhwju7RAgsyVVRb0iWG0rxgKUZyDt8mlU5DpXxS-OJxbabYqkckwxD5U3IzAILQeSQwoZLAVZXUgCsal-8nplpxqELQOLUMobBFBt1ZBqE6i7Pi5nrg4TM9pkuHgthbzsGW7zGG6ineHPLENfaJeo3Rh9Sq5XnNvjpwQ2BA2cCEEZK830j1R6bwHorLbfD0m6ao_wZDYPi_l4hBoSL_pI53yx21cshGplv_-FmG78Ab_iDThdWLzisT8EB_jw6HeVqiSlTinh_b4B48CmaIKtP5MGPD0eyDgd5jhBi-yDqd1k85b8nhqcYz_j0YjVvJr_Ld9RUNaSTYoJjIgbYI3rVhTWy4ICoehJ4TgSiTNeiNEVPFrRNedLVSpqv9Ef5A4Xsfi1af691arL7fjkOPX80-RxvviYholXyhdDL5sQGq_ehmWu2Mb2f5w7zmMDXoUWVyZTDSw8zs17ZCgxhJpjINjJ9bH4hPQt9iztY0r20uZqK4AAHaGiO681UDt8XIoJUyMIgfNPfDJJUVUO_PDbFc_uVSpNmNYxoogT7hu1vUKTXeeDC6y_3BF1WQBZ69OHKN7anWYptie8nSw0KdTxhCSNoR9GDf8Hfu3Ero1lOSszkzJzrXWvTz1Bv8BCXnwHKz60ZZtm-25Ue4HkHsKjaO6jhfGbNeQ0JTX4R0hGQ0D3DXmTMts_tAxOO3KgL1Yoqwx4IEooXWGVSRA05D7mzaRVaYA0VaTDOrJFTSi76nHwWQ-MCiQJcMdjBKpDj371Fm5h_GZbXZCON5ChCNT7DfpRGY7_xYRuwmMcMxm3HbcUf3EDV4ceIL3Ist-JyRPLnR921QFswRIWEPnC7pqEdTZTdHNqXNAMenz_pFpyi4nE12WJ_z8zeWm7xeWxsPo2I6XrQ4OzoOkSrOllQdg_Kp1caMb0cBcdHAj_TMX1axTodlqTI51rKeppG4j84VUyJNaBajzULzESfHcUYTEPA_dfkKcAuvHejadIrw3_B84hbry08ZBtIHePixrtlYsEqDg20MQO0LBMrJr7FObhr3ZJBj84dvxIsdzxVlVAwoKxmFd8Y7bC14vnvcmzNEPcpBf0loyiuYI-xRtrodalO0LBdqGQog-nPY2AhG4asfoZVS4JeTTnn1m0ZPjdqCiUpbQaVtcLVbyl4GXBNZWfMCMj-iVi6l0sr36M7B7QlyXH3hSN-me75taM8wCN3l3CP3ntDrbOSpYA2nibuWqDOZXKArWl8U1ZaiZiK3ABNm73jXp6M7oDe6lB2hcesVTNy9nLbE9m_xfX3dC3k99sIMO1_XzNcXy6fn5y9tSf5YO3WWdY22_1JI3nWypaAraVAC9dwXBUStDAyJC8f2_1vx25NEVweGNsuoU9xvlW4eYN8VeInTRiI2XirA2ZZTWCSIsTEIGqbSD2PzUmGATbE7FaJEqnk9sFVRXcaTePexO6E4ECRYlKfeYYS8XKL9eB2kCI7TjxpualyAJqwCCzRIx5csjFBwDSLLT8eNxg6zb8R6c66oLXudC5gqo3tARo9ZguzGcgA4LJOcfZbAlyHGrb0wToy3r9_d1iJq6W2DqZIwHQo9Sx6WeYb8MZ3KWigbifkO4cf6Ej_0Z3DkAOnv6JO5TGBz-iHX9LlLLaLHiMATspZRSyHlfAXq4xc1WRSoaSPeSeCohHfUhyZlH6TdjIwWKyWf1rLsZmTg8kdK527Nm3QXALc_qd6C6-GhgFwwJJQG47wp2ogK9Eqq_A7MLqfxad_9AijQQYuce5qJnssQoj5kFCY_Ak0BaKSjpPw42r8tNzu-wr-55_frkOhSfV8-rMCIJkH4skedlFXW_mD54otU0rfZqyDFoavM3diBLuiocpuzxIcJ403pncvUXRiqQjmN1eefKNaLVWu9pYkyUzKIgNKedxeY4GtSYdb_S5dD_evB934zR215VG3O5wLZkiionxBTqz7ZxsytYLMVWwUaXpivwYChiKXApxQy92s4kJATOOrbMmIPRKo1wogtRmoQn1vf0VaEF0d8rWm1isUfanaeBjNsNciK9EpK5kkTzeqvwrDblG2sQQTMS_iROjXWEblvRq2aTLRn5-hBtKEats0rsg5NZswod0LY0I03gwPSpv1K1LOWl3eBo066eGKtvHKlKo2TenuPTTuo6oRW89wrfUmKLfxPP5rD0u1-w5mpGO7013A_janJ3hEMQBp_AvRhSaPAEHPk5EAv2WD_SgaDODRG-SJDf5qX_ZpeYVj3Oee7r3p1eBBzyWiskl-ybEjc4WZ2acJfmF4y6e8RWPYX4NIEja1NNKB8pAb49HRjz2NPwLbhBvM9PAvKi83VLPeEOtpX1CWSw3L62y4zpVe02AgAUi34ZddbuspAyCotVUTQ52XITgp6LjRzplmp886cXiqiCQM6ClF83qdWxBrsV23Q0alWML6DjeGM6brXcm0IQ2DbJ0z7W7vWp7r4icr1NJhHPqStgF5om0LSIqwCBpdneAoyGTbtlvjVIAQFty6VUQ3w7Y8Q74HTy7JUBrv7dzXwrmiFbGSBCZh2DiyoXCEdXDjWOgIMHaqvphw6WGBC-KblQ9ARMxbEusFZTl_mJdagqYKlbyec4d3FT5fk1Dc_ro2UpEztYYshgXWqK5zE4ymELe1A7h4CZqKRjl84A6ixp3K6TWC4HcZwKfjR1Z6meMGJg2pdwA2cnDUH3eDEvbzsL4fxFsOPLhX-WsxdSnhrrp_lKhf_AVYGR4T3eRWAUUEP4-Y2YofpqsYU1cRw8NFjF6lStnK5CO_ejG94LKQ3nDp2tqIxppEqL68vLEyhXqNcU4St4rBPhY_tCP9UCSuzb9PMuOGl-7p01yEzGnn43wMTucyVves84GTCJlkVazP5jMPvjJIXimvJBq8F9p3k0Su7QVOwC2xGnoQt1DcH1qsOyPyiS3SstnzL9HbS7FeNylzAiWE_iZBH5XBjTbeXHEjErg2GtH4M2VJDMVmDbS44YmFnuZ54VvQiM9WC_8jzSZHHPBkcHASWSO87Z4tucCCCF0xPwvdf4qBMyQjFhC-QK7GvNmvRGuzzzl4ZEsioP58WPliSPH_wlhtPhI0NaJdoRzT4oFrhf4-pwuqj0kMv2c8xv2DzcTAV3iMb98zjXKunWyqp1Ju3FpNhZPAup2VDqhSyHiCsT7nM9yYUusoVDxQMmdOqnCpfV5EWyZQb6OuYvEDieDAQdrsNqYkHrn_N5mLW7x618-7tPdyOE0T4giER-DNEM77LesuHvSA2geCYC8yRUeuxbuDQjzzkNVjQP0lso-awEGD3Cy6U0W1fU8MbA3kdzeQuRNjl4gbFXNIbGRbFr0XwsGCv6OPMdqB-vi5ktwQtxmZsLWFug7Wa44vCy6ZPBuk7dvGcBGKdXNOwcUlx0qS9v1QLEWEMJicFB2mRZB1z_89feYEQhY5JVeX48TeXxExaMoWjvpYzmMKrkf-OfObhown5jXo-lVsOjgsE46ah2bk58hOnuZ6Gfq3NJgZXMENzJChziPwPOORs4a7xcdiMqBwXGvtRgo31xwqvJgIlP75dZt1LJ_bcOs8UT6QYmIzvfbLoCEenUrUE8rBfELeZe4_FprkJbyXsDlTtmgyQDK3fEanbh0ubYOetUfeMbKSdKJSLWD3lqNQCjthizQJUxZ4nS-8E317Oyq7MwgbpnzsTWKVegumThxs-6PHjGIPwGMGYnjVTiU2fc3P-ZSY80AMqUEfjnUGvdmkTpdOlwc85cfUGfiWiaE_C2UMQTfdKveyTAh5I4m4uoXp-6Lwz0faarzpSMD8o8PQ4b_pRNqos6r67xVR3_lUySbkpOBZedRjLPbrjUhx_33evyesqugLQ2xI_DqviD7VxzaxREQvt9HXgGLM9-yFIO2occX5QawkLBuJjEGE0d8nMco06Cn0U8s3ZAHCYkW-SuesiYCP9oIBL6x03xDkIZRwlu_Xiq67QyX-_IfhKvSPQ40MF-LTtOjWF-IjyMP6fNQvuLB3nsn_rng62AdXjuObiWkwxUsEekD0cGjNm8Cw4feej_3FTjO8g3rbX4ODndSSCQVUH7xKCtija-9yhNR5c14lHuC4Xp2YJngbpTi8eVAJ4zzuF6ie1M_co6oZwCFEL7d1USqkDUO9Nmh4qY3BZV-SzGIq71Iebxe7IHWdpt6yO2fD4QTMqerg3LaabZR9xIkUVnoe7A7Rc1ztDtWsPEUeX_BltaktXOVvvZqwhztQdH7R1uA3b5JLkoiHxgrDFMwYm2PWZ7yp9eUFVXPoJl6IaWxB0PV_IdSm_o0aNyGbjqJqqdmY3ersybNKzQ9mLglKZ4zT4X4ClMcCGDh7I3uO5pvFWTnRiI-lebf4HL5l67bu5v02eg5wAOgIMV6FU7B_oH-ftptEyIq0VnX9QpZh2ubOs9g_FwPvMecJugPqveQIsHICMH5PqNlBh7g8hsMF-qhsV_bwVJ88Vd-3JfduaFUjdcEuooVjr8nQSh61uX2Q3FF9G_5J8hIiLhn02ouGNYE_o6f9ciX4pdbwjQam0iINI1_qqAQFqgu0FOxDXGlbDU28k3aEOD0HlRZbp1F1aoRBbzaLO65R2KEghbE0LsP8SefeqEt3OrLwQfLwxi0rAwQM0wiK70muQSeX1WPxTkX0kRtLmvVE0sGNKz9CrVoCGFloJ7uDn8N9HqN8O9Y4cb06yBafcHf-gZS5P3opLA3WIc69de0gfByKt21Zlqb_8Kd-pV8WI5w3C5Bb3q05yM4AwU5AWACGFlTlZ4XEtkcaIl1wiNy1bYMTrXxofuLsJFWaXVe5UU7F1QGWtPtgKswxjt6dMh3bOPDh56NFT6ztfVNi_Q-KuIl290McC1hSA0SRw8DKzHYhDGx5nf925ifKDcqvmSKUy7UBY4Yo7NNM5EXUGghg2KaHdtRDX9q8uWALoEITBMkQes9qySiZbC-SxFHd5vsBrMXSvMCxtozJdJkjhJ0RLsmw1mzcVzmlRdhAoFGypaVuzCQHS3bdDJa1uk18NummrLU232Lk_tut3FYtsFMDb0GpWDbB34iTd36zUjw45Ztmf9hpWaPxvAvI1CMz4J-pmgoGDHvwbwQvhXal1dM_t2O_Vdc6YOM7-2DNQqA6qomZNS2iQraYeOTrHxnHBaZVZZiCPUn8laBLg3OqUWt6M45gt_zOjFYNu3mvIk0Ith3gyLVoqMi6TYQXn8qARAdqVPgXcGNQNt8w6TG4jMDG6SPf6ZPvFVUVQ7jVU0ty1jZz0GR0qaaOXFNHUdQDVqDMz_as9vFAjMYSIoUr-AopF-xQPiFdkAd1qcuRY8EvppFZnad3kXcAOzV-eMx2T1TiCnGzENLROUbnHlkWYS3yVJa72AKjQxwPsodleqQVhbJ1E3v6QG8tHBM-wEd5qjBpfNM2bjeNY8Uz230clZcLcCHknAinG0BqEgG-m4BZzygTGCEuuh--eVFOoIDXF9wcHuMUuihN83NZvXRWxdJhoh7yslFVEmp5OYuzmqNeTKHrE58tksZYr6_LyxCYz4D_jyy54wVMeYo5PJOQswZQuQSMj-qHMFHexK6jlHxgyKH8mH0HlUISTvwKiTZz8SsD8bisBQF-dA2eAeSKmQHFuTmUUDtzNt65Lr-T4mAtBNFMzy7Q2hYAbz5s0wFdfsgo8tNEZMKZsvBHyhbtLtVzu2Z55_4xU5VE1vn2ON5Q0bV16LeTBlf8luKzvkPa1kmu3T_OLYc7lS-tRBt-PKW85t6ZIEyjt7guHgymEeVo7U43k5sH6y0tv1vTku7CY9haGMgSbGBoFhOzw2xTeNadwc3-8cFVN7WwsfJGedMEpfMKsxtkUXuKSDknrWVKPeUIuJXsUk5RwjCCNdisOrSqaPDu9d43HvvoCPWjucQb_rZwlYVs0ok4I8CChxKL8YWiTv2W2OAtJR02CG4Iy27KJMzWXsR7vuoFBeY4XQQBupJTwoW4KerxsF2lkRL1Y8Lpe5K4qPVBvIq_pdhD1jaQQZSzvXxEvRTJ2mvyUtVytcvgsTWOaAFqXCp8rHHw6aTx_RqRDB-C_zEJvlpRG8in5WGnUhUtqLna1l-k87dJwuPj_fvqAeX7przDV04t99DF0mQaYcSWoHEau43SukVBI24p0CUqu1_4LUBcpFaGfkse53bww0dEaGMvwjO5KUbr7S1e1GhnRSqiRWiDUKIhEO0MQ4OGyRUElb7OWU250olqhS5ohw40awA71aKsHpXI2LeyGfc-NNROUg0eiGfCRnCONngyNSZsBBN5l5AJymrWt7VJIvisS100x_2fCgPzd7Hkc8vcXxFl0L4L7lUqdrzzS2hsOM4XnR_2nqLAGK7MQbpgLHHVtXv8bomZT9fPYZvFRYZto2t1XE0Q6N6JwUwNGFVFNWeAX_yYNN7KzdCAz7T0frB98vtPeahdVjAyDjrh_yJCnB2zKbL2BSvE9hMIfQ-r25dCvz5oWL_mi0LfmVFYFrn_JkTFINH7FoeQLgqB8nY7gMgDUXTe3Ejhmue0DkA-0pvx5u277ek2S_eYMtr9bHwuiR1eR26c73t-B5L6EFyy6ZQvJC8C4bek9tckhPxSgufYg7kKr3kTTUTefnaiEFYGV7jcW2IEo9GQ_5s5L2lCGb2HOdD_l3rHiocev5nnTID9NYjoyrK6NEEFm0tCBk9QQ6V9odICBc0eiXg4ODHHsSLg4Iy7oaXYAe3Gv6oiQ7T9brrgFke8b4a_asM4eTWqj53_nb_hljOX_uei3pcPSSnxvmzGCFyIzQsvNvR_itFNNdsBUbtoo_m0GwqddCJoCsLXQj_F7Igb2NShyG4CB9yFxns01WlzpE7MB-JY1hiNKpvGgRGinB-7ZnMjIAQrmxHbreC1v-KCvWDXwQqxnxV5G1yTQnazwGpj4VBhxB8bmsqcJ9gGwQNHXMopLFCYzsErRot7bVt48BKoVd_6YtBb2D3cNVasZ7gjZo6k3KFGWlK1sKw-DYGBUpn9CSv-5-kB6oOISW3TIULg0Ig0eI0blhKkEGAcHSeDdweidF3MFcYfdmNSbveSHb3u4GnjAl8VdJl5dFKoncUrHO9IstSAkNS2K1pYlnq4t0EdKYdNPbyAekdMA0j5q7MjsYJ6YQHZ_7p-QQXueH4YJBqh61nZn5hWqMrcBVLh8wB71qeS8LLFe3zkOvxVkREQoocPOi5gsjMZxqckEu4A_dsCHx8n8iMbh61JMm4KntPYczw_ok5wRZwq_lFNvSaJLE0z2NJYPnkgtNURHtZfXA0IbrfCjnHTA_DJmfgAz2N4ztC3EjkbepQ5y7sLWXofx1bvwJ7aWjcQi41s2iNdLyKZKDADqv_1fonq8Rn2lyoltJ-Q_SQZyFqPFUGIIyXNRId3IS7TFLQ8nEEaPpgKK2qndCT9E6e-NzZALXtc5eB6tvCK68SKHgAmTppra3APrI8fsUGa7Jk4wu7b-2KyHE-o1at4NfnyL7dIpQeIxXR6s-Y3wDJkCikNrFSMXJGlTHk3LsOo7z4qpjl2XNs3P0Myvn1cQJgjDrDZO5tqSq8OOI_0oiwZUOmJorRZ7Z3fMfZ0_P-s0SjXg0MUNNXFdpDRQm4AglHhCiblkACwPi73QoWQO7s_yh1QeYcDaR7BJcImpiLEt66LCgnDx6jcr-Sjxt97k6SK30LR0tISXM91Skw8wrjOfpOz6opHraECFKlgqP0NcecQGskPGEdqujyXlSlXSCjlr42zhUE7OQAPMpNjP_Acmj6f67VDSf7Dw-QifSHLH0icbCa467vHmQtMme8ARhP_9YxV_URt9cGO6mzdjpIO4PM8r5bn8PS6U8QHS9BoLapQiEugh0MBc36SIzXEVH69pEDXdQmKmUsCTmhxyZm6SKOFtcPfhRkVOYrfWrUHMbG48tz5PNRK0oC5FJZ4xLhwt3y_RTlk8SVeJg5Vuc0ewaXhCTWaFr1QmUw-u6vIYXhM49L57dPAf3O3myuNOPvAb7UW2E656bYzuKiO64-JTKKPW2bt2SvYT_tLjZxFf4z30UpdCLx19nUEa-PA7H76RJLRBLgjFJFLPDTFggBJ8u_VBJ5s2d7hH4qUfhkLHCI_tdmmp2vRaIIAUHBIV0qL9DiJFnIXCvv6lSIAUbUFxWXVcGjh-7iE8YKsh5kc-kcD8vgXa8FHN1agKfQnkUwnk9Qzf9hj538wOEQVHVOHtc2502PfsaI9gkjAkljmDliD6aAP21fDPk16I7E_fpV8tl4Ia625VXnwiQD0cQVOiH9l7XqgdT-n4lIhxiCUhDgUdJFBTXTFVt2qcNMHIo0AFrxgW_gMFQI2t9yvXQerjk7xwQH5fGvoYP6mJDx1AeVpQE2hIpZAWU2Gk7I7beWyyFXbaWZpPfTPUfPAGRoTuUAB1n1FJ1CefQnHDXQL7ZZJkyzHo3swVDOeuZZjYt36UZjPjod4jqa0AJicZ6FGwuWQznBu60waDjCfACBe-28nRFlfrXTHXpH3jVw89tkvIw6Aj4Xw1wzOjsOhksAAzYhV3WPEvJJ8vuMzfoEaf774ICF5jP7gk-Ptn_Qe1bDOaHPxdp_H-iJ5-pJ9v05gxtyLdwBa7nblKoTYW0PeyY7EJCqTs-C8aGbyKc_55KRPPMMTP58gg4Rj4iBW1ewNf-7vxD2eW-5ttbQRQKfUG97xFv4WxsjII9IERr-0-frGvG4a5Di_D7jHlFTbVWubcPf9K2tiOeNy0pmqJj8sK3gXXRFStIRDVaedlhTHDOyIMa4dG_kQLh8jEGXILePbnWX7gydJMndSYQiaYCh1w0hu4P9PVUmTA3VcEOKIb9MzuPD7-SWkTtbnDI4qCRDH3mG0wiqS8jHln1VIrKUZtHugQUj2uAUn8sMunUuRdkrBnwUDSVH75nmkGPuxdVO9FZzsEBHQtmt88xuQ-HNEXjtp2pkBnCsvhaXdN5XLgFB0lSHEoXxDigYAy5BNQO4-aqBAe6rNyW4aSDIhrYukLd3lC3-vlX9hGpidApZ3nnZuTh5msG2L4d9XoG7AOulc3KiHH1vFuoQr8ZoazUYecPxM4ALJDYCH0X4fy61tGAODPaBPxgbOqXoJySpuPmeBLI3G2qzXVGaXicJgHgx0AYXEA-p-GiLqX6f9BewckC9cEAMNrsoCiiqitlTIqxkpLgb6FIyEtQSDbSRHYdD0wDE2QZ8odp6gJX3H4grtXpm0dXDVlHdBSDv1RnZRfg0Dmm95zvXdw8uK2UmoLMz6XoXrkA7cwgae0BRe4oJkTR1FuMoftsrOu7ACTHHuNFjLNYgBbMeEC7h2QRgZUmAi1cp_gUWa2C4QqfmlKMiZ_eIzbuDc-seymhOYoq8TxTYBFKyPHIpOlQXZMV7ZGLFnHlnhBPTDge8vO79X6i_lhKKYiIE9ChpdEWlJaunrsDlTlqd9NjqTyZv6T3z22tKZHiMMu44enpXGp7FcmWXc1KH3XCK7EgEdxhiE4sGSsT4SMiwyjG5Lnzm3NJ1awJBxsIu2ED4lpzAEh3dxcOQzezH5Oc2PnobnwO9zszpff9y7TGxcVCjKdVK-p4HvDpb8hqy6v5bQRd9ObzqT67a3IIVSPec2nDtFRHXAzHduLDBfIYRi2q0-_VpfdHvItCSSdusFJ11Xh5RrotGj1zROaTrH-ZZEPNwQtrGlJhDZcUMmG9gCaV3SFgZcupCTORl5BndNhHC6fPvJIf3WID07o6Hjaq28AliM4V-IX3pivVBc0noOSJb8YMfUJ1FHvEPWoQp0AnkmUCCTqBobMlfrO-VNbwD2A-TdWlzQqqkVachVj80adtpxOSIbdZ3EM_OE5F_N1mSlaM60WnScs90hkFuFf8QP8YQAcxnK-4hkMO0fxUO47cYAm87-BI7KOaTgXdHvXAEzM2ca5C4Z41r7ylS2iADGv5qCGuWbrZzZNyceQdirfFeOiPXZpPMUh-kuaZZQPhLBH07MPyTM_Bs_xlag7MnHXC6NKWF54xqHGqpJqCQY7in8rAfWix-VPOz_um7EFchmIAApy9hvfHDNqAkt4e1gQqRoJJSM4h9FE2ilaAiZccP3vhV2iNN4oJnQz9bxqtmhIAwfzQkfvs-nUc6y3vpXGhQ2onZUDhhRrHwMWSaJw_MBDBdulmCSMOwRc-0hCYqO0KySYJvDAXgGlKbLzPrYv8R7ZbxnQFnlHEE0Yf_Leh4= \ No newline at end of file diff --git a/BENUTZERHANDBUCH.md b/BENUTZERHANDBUCH.md new file mode 100644 index 0000000..053639e --- /dev/null +++ b/BENUTZERHANDBUCH.md @@ -0,0 +1,385 @@ +# PointCab Renamer - Benutzerhandbuch + +**Version 4.1** | Datum: 14. Januar 2026 + +--- + +## Inhaltsverzeichnis + +1. [Einführung](#einführung) +2. [Installation](#installation) +3. [Programmstart](#programmstart) +4. [Die drei Modi](#die-drei-modi) + - [Einzelprojekt-Modus](#einzelprojekt-modus) + - [Batch-Modus](#batch-modus) + - [Projekt-Merger](#projekt-merger) +5. [Konfiguration](#konfiguration) +6. [Troubleshooting](#troubleshooting) +7. [FAQ](#faq) + +--- + +## Einführung + +### Was ist der PointCab Renamer? + +Der **PointCab Renamer** ist ein Werkzeug zur automatischen Umbenennung von PointCab-Projektdateien. Es löst das Problem, dass PointCab-Scandateien oft kryptische Namen haben (z.B. `1.lsd`, `2.lsd`) und benennt diese nach einem einheitlichen Schema um: + +**Format:** `[ClusterName]_[ScanName].[Erweiterung]` + +**Beispiel:** `EG_Flur_scan001.lsd` + +### Hauptfunktionen + +- **Einzelprojekt-Modus**: Ein einzelnes PointCab-Projekt umbenennen +- **Batch-Modus**: Mehrere Projekte gleichzeitig verarbeiten +- **Projekt-Merger**: Mehrere Projekte in ein Zielprojekt zusammenführen +- **Cluster-Bereinigung**: Automatische Entfernung von Suffixen wie `_re`, `_li` aus Clusternamen +- **Detailliertes Logging**: Vollständige Protokollierung aller Änderungen + +--- + +## Installation + +### Windows + +1. Laden Sie die Datei `pointcab_renamer.exe` herunter +2. Speichern Sie die Datei in einem beliebigen Ordner (z.B. `C:\Tools\`) +3. Kopieren Sie die `cluster_cleanup.txt` in denselben Ordner +4. Starten Sie das Programm mit Doppelklick auf die `.exe` + +### Ubuntu/Linux + +1. Laden Sie die Datei `pointcab_renamer` herunter +2. Speichern Sie die Datei in einem beliebigen Ordner (z.B. `/home/benutzer/tools/`) +3. Kopieren Sie die `cluster_cleanup.txt` in denselben Ordner +4. Machen Sie die Datei ausführbar: + ```bash + chmod +x pointcab_renamer + ``` +5. Starten Sie das Programm: + ```bash + ./pointcab_renamer + ``` + +### Aus dem Quellcode (für Entwickler) + +1. Stellen Sie sicher, dass Python 3.8+ installiert ist +2. Laden Sie den Quellcode herunter +3. Starten Sie mit: + ```bash + python pointcab_renamer.py + ``` + +--- + +## Programmstart + +### Hauptmenü + +Nach dem Start erscheint das Hauptmenü mit drei Optionen: + +``` +╔═══════════════════════════════════════════╗ +║ PointCab Renamer v4.1 ║ +╠═══════════════════════════════════════════╣ +║ ║ +║ [Einzelprojekt umbenennen] ║ +║ ║ +║ [Batch-Verarbeitung] ║ +║ ║ +║ [Projekt Merger] ║ +║ ║ +╚═══════════════════════════════════════════╝ +``` + +--- + +## Die drei Modi + +### Einzelprojekt-Modus + +**Verwendung:** Wenn Sie ein einzelnes PointCab-Projekt umbenennen möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Einzelprojekt umbenennen"** +2. Wählen Sie die **LSDX-Projektdatei** aus (z.B. `Am_Upstall_4.lsdx`) +3. Wählen Sie den **PointCloud-Ordner** aus (enthält die `.lsd` Dateien) +4. Das Programm zeigt eine **Vorschau** der Änderungen: + ``` + Vorschau der Umbenennung: + ───────────────────────── + 1.lsd → EG_Flur_scan001.lsd + 2.lsd → EG_Flur_scan002.lsd + 3.lsd → OG_Bad_scan001.lsd + ... + ``` +5. Klicken Sie auf **"Umbenennen starten"** +6. Nach Abschluss wird ein Protokoll angezeigt + +#### Dateistruktur (Vorher → Nachher) + +**Vorher:** +``` +Am_Upstall_4_PointCloud/ +├── 1.lsd +├── 2.lsd +├── 3.lsd +└── ... +``` + +**Nachher:** +``` +Am_Upstall_4_PointCloud/ +├── EG_Flur_scan001.lsd +├── EG_Flur_scan002.lsd +├── OG_Bad_scan001.lsd +└── ... +``` + +--- + +### Batch-Modus + +**Verwendung:** Wenn Sie mehrere PointCab-Projekte auf einmal verarbeiten möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Batch-Verarbeitung"** +2. Wählen Sie den **Basisordner** aus, der alle Projekte enthält +3. Das Programm erkennt automatisch alle PointCab-Projekte: + ``` + Gefundene Projekte: + ───────────────────── + ☑ Projekt_A (15 Scans) + ☑ Projekt_B (22 Scans) + ☑ Projekt_C (8 Scans) + ``` +4. Wählen Sie die gewünschten Projekte aus (oder behalten Sie alle ausgewählt) +5. Klicken Sie auf **"Batch-Verarbeitung starten"** +6. Der Fortschritt wird angezeigt: + ``` + Verarbeite Projekt 1/3: Projekt_A + [████████████░░░░░░░░] 60% + ``` +7. Nach Abschluss wird eine Zusammenfassung angezeigt + +#### Erwartete Ordnerstruktur + +``` +Basisordner/ +├── Projekt_A/ +│ ├── Projekt_A.lsdx +│ └── Projekt_A_PointCloud/ +│ ├── 1.lsd +│ └── ... +├── Projekt_B/ +│ ├── Projekt_B.lsdx +│ └── Projekt_B_PointCloud/ +└── Projekt_C/ + ├── Projekt_C.lsdx + └── Projekt_C_PointCloud/ +``` + +--- + +### Projekt-Merger + +**Verwendung:** Wenn Sie mehrere PointCab-Projekte in ein einziges Projekt zusammenführen möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Projekt Merger"** +2. Wählen Sie den **Modus**: + - **Einzelprojekt zusammenführen**: Ein Quellprojekt → Zielprojekt + - **Batch-Merge**: Mehrere Quellprojekte → Zielprojekt +3. Wählen Sie das **Zielprojekt** (in das zusammengeführt wird) +4. Wählen Sie das/die **Quellprojekt(e)** +5. Das Programm zeigt eine **Vorschau** mit Konfliktauflösung: + ``` + Merge-Vorschau: + ───────────────────── + Zielprojekt: Haupt_Projekt (5 Cluster, 25 Scans) + Quellprojekt: Teil_A (2 Cluster, 10 Scans) + + Zu übertragende Dateien: + - EG_Flur_scan001.lsd + - EG_Flur_scan002.lsd (Konflikt → EG_Flur_scan002_merged_1.lsd) + - ... + ``` +6. Klicken Sie auf **"Merge starten"** +7. Nach Abschluss werden die Statistiken angezeigt: + ``` + Merge abgeschlossen! + ───────────────────── + Cluster vorher: 5 → nachher: 7 + Scans vorher: 25 → nachher: 35 + Dateien kopiert: 10 + Konflikte gelöst: 1 + ``` + +#### Konfliktauflösung + +Wenn eine Datei im Zielprojekt bereits existiert: +- Die neue Datei wird umbenannt: `dateiname_merged_1.lsd` +- Bei weiteren Konflikten: `dateiname_merged_2.lsd`, etc. +- Die LSDX-Datei wird entsprechend aktualisiert + +#### Wichtige Hinweise + +- **Backup**: Das Zielprojekt wird vor dem Merge gesichert (`.lsdx.backup`) +- **UUID-Regenerierung**: Alle übertragenen Elemente erhalten neue eindeutige IDs +- **Cluster-Duplikate**: Gleichnamige Cluster werden zusammengeführt + +--- + +## Konfiguration + +### Die Datei cluster_cleanup.txt + +Diese Konfigurationsdatei definiert, welche Suffixe aus Clusternamen entfernt werden sollen. + +#### Speicherort + +- **Windows**: Im selben Ordner wie `pointcab_renamer.exe` +- **Linux**: Im selben Ordner wie `pointcab_renamer` + +#### Format + +``` +# Dies ist ein Kommentar +_re +_li +_mi +_mi-li +_mi-re +``` + +- Jede Zeile = ein zu entfernender String +- Zeilen mit `#` am Anfang sind Kommentare +- Leere Zeilen werden ignoriert + +#### Beispiel + +Mit der obigen Konfiguration: +- `Flur_re` → `Flur` +- `Bad_mi-li` → `Bad` +- `Küche_li` → `Küche` + +#### Konfiguration neu laden + +Änderungen an `cluster_cleanup.txt` werden nach einem Neustart oder über den Button **"Konfiguration neu laden"** übernommen. + +--- + +## Troubleshooting + +### Häufige Fehler und Lösungen + +#### "LSDX-Datei nicht gefunden" + +**Problem**: Das Programm kann die Projektdatei nicht finden. + +**Lösung**: +- Stellen Sie sicher, dass die `.lsdx`-Datei im Projektordner existiert +- Prüfen Sie, ob Sie Leserechte für die Datei haben +- Der Dateiname sollte mit `.lsdx` enden (nicht `.LSDX`) + +#### "PointCloud-Ordner nicht gefunden" + +**Problem**: Der Ordner mit den Scandaten fehlt. + +**Lösung**: +- Der Ordner muss `_PointCloud` im Namen haben +- Beispiel: `Projekt_A_PointCloud` +- Prüfen Sie die Ordnerstruktur + +#### "Keine Projekte gefunden" (Batch-Modus) + +**Problem**: Im Basisordner werden keine Projekte erkannt. + +**Lösung**: +- Jedes Projekt muss eine `.lsdx`-Datei und einen `_PointCloud`-Ordner haben +- Der Projektname in der LSDX muss mit dem Ordnernamen übereinstimmen + +#### "Zugriff verweigert" + +**Problem**: Dateien können nicht umbenannt werden. + +**Lösung**: +- Schließen Sie PointCab +- Prüfen Sie, ob andere Programme die Dateien verwenden +- Unter Windows: Als Administrator ausführen +- Unter Linux: Prüfen Sie die Dateiberechtigungen + +#### "GUI startet nicht" (Linux) + +**Problem**: Keine grafische Oberfläche unter Linux. + +**Lösung**: +- Installieren Sie tkinter: `sudo apt install python3-tk` +- Stellen Sie sicher, dass ein Display verfügbar ist + +--- + +## FAQ + +### Allgemeine Fragen + +**F: Werden die Originaldateien gelöscht?** + +A: Nein, die Dateien werden nur umbenannt. Es werden keine Daten gelöscht. + +**F: Kann ich die Umbenennung rückgängig machen?** + +A: Die ursprünglichen Namen werden im Log-Datei protokolliert. Eine automatische Rückgängig-Funktion gibt es nicht. + +**F: Funktioniert das Tool auch mit älteren PointCab-Versionen?** + +A: Das Tool wurde für PointCab-Projekte mit LSDX-Format entwickelt. Ältere Formate werden möglicherweise nicht unterstützt. + +**F: Wie viele Projekte kann ich im Batch-Modus verarbeiten?** + +A: Es gibt keine feste Grenze. Die Verarbeitungszeit hängt von der Anzahl der Scans ab. + +### Technische Fragen + +**F: Wo werden die Log-Dateien gespeichert?** + +A: Im Projektordner, mit dem Format: +- Einzelprojekt: `rename_YYYYMMDD_HHMMSS.log` +- Batch: `batch_YYYYMMDD_HHMMSS.log` +- Merger: `merge_YYYYMMDD_HHMMSS.log` + +**F: Was passiert bei einem Stromausfall während der Verarbeitung?** + +A: Die bereits umbenannten Dateien bleiben umbenannt. Die LSDX-Datei wird erst nach erfolgreicher Umbenennung aktualisiert. + +**F: Kann ich das Tool über die Kommandozeile nutzen?** + +A: Aktuell nur mit grafischer Oberfläche. Kommandozeilen-Unterstützung ist für eine zukünftige Version geplant. + +### Merger-Fragen + +**F: Was passiert mit den Originalprojekten beim Merge?** + +A: Die Quellprojekte werden nicht verändert. Dateien werden kopiert, nicht verschoben. + +**F: Kann ich den Merge rückgängig machen?** + +A: Die ursprüngliche LSDX wird als `.backup` gespeichert. Die kopierten Dateien müssen manuell gelöscht werden. + +**F: Werden alle Unterordner (Previews, etc.) auch gemergt?** + +A: Ja, alle relevanten Unterordner werden übertragen. + +--- + +## Support + +Bei Fragen oder Problemen wenden Sie sich an Ihren Administrator. + +--- + +*PointCab Renamer v4.1 - © 2026* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5ae84ce --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,171 @@ +# Changelog - PointCab Renamer + +Alle wichtigen Änderungen an diesem Projekt werden hier dokumentiert. + +--- + +## [v4.2.1] - 2026-01-16 + +### Behoben +- **build_linux.sh überarbeitet und getestet** + - Verwendet `python3 -m PyInstaller` für bessere Kompatibilität + - Verbesserte Voraussetzungsprüfungen + - Bessere Fehlerbehandlung und Statusmeldungen + - ✅ GETESTET: Funktioniert erfolgreich + +- **build_windows_wine.sh überarbeitet** + - Bessere Erkennung von Headless-Umgebungen + - Automatische Xvfb-Unterstützung falls verfügbar + - Klare Warnungen zu Wine-Einschränkungen + - Hilfreiche Alternativ-Vorschläge bei Fehlern + - ⚠️ HINWEIS: Wine-Builds in Headless-Umgebungen oft problematisch + +### Dokumentation +- Build-Skript-Versionsnummern auf 4.2.1 aktualisiert +- DEPLOYMENT.md mit Testergebnissen aktualisiert + +### Bekannte Einschränkungen +- Wine-basierte Windows-Builds funktionieren nicht zuverlässig auf Headless-Servern +- Empfehlung: Windows .exe auf echtem Windows-System erstellen + +--- + +## [v4.2] - 2026-01-16 + +### Behoben +- **Windows build_windows.bat komplett überarbeitet** + - Verwendet jetzt `py` statt `python` (Python Launcher für Windows) + - Verwendet `py -m PyInstaller` statt direktem `pyinstaller`-Aufruf + - Korrekte --add-data Syntax für Windows (Semikolon als Trennzeichen) + - Verbesserte Fehlerbehandlung und Statusmeldungen + +- **cluster_cleanup.txt Parser verbessert** + - Unterstützt jetzt UTF-8-BOM (von Windows-Editoren erzeugt) + - Robustere Behandlung von Leerzeilen und Kommentaren + - Gibt jetzt Anzahl geladener Einträge aus + +### Hinzugefügt +- **cluster_cleanup.txt erweitert** + - Neue Einträge: `_part_1`, `_part_2`, `_part_3`, `_part_4`, `_part_5` + +- **Git-Repository Setup** + - `.gitignore` für sauberes Repository + - `GIT_SETUP.md` mit Anleitung für Gitea/GitHub Push + +### Dokumentation +- VERSION.txt aktualisiert +- CHANGELOG.md erweitert + +--- + +## [v4.1.1] - 2026-01-14 + +### Hinzugefügt +- **Cross-Compilation-Unterstützung**: Windows .exe unter Linux erstellen + - `build_windows_on_linux.sh`: Docker-basiertes Build (empfohlen) + - `build_windows_wine.sh`: Wine-basiertes Build (Fallback) + - GitHub Actions Workflow-Beispiel für automatisierte Builds + +### Dokumentation +- DEPLOYMENT.md um Cross-Compilation-Sektion erweitert + - Schritt-für-Schritt-Anleitung für Docker-Methode + - Troubleshooting für häufige Probleme + - Vergleichstabelle der Build-Methoden +- README.md mit Build-Optionen aktualisiert + +--- + +## [v4.1] - 2026-01-14 + +### Behoben +- **Projektmerger LSDx-Zusammenführung komplett überarbeitet** + - Cluster-Duplikat-Erkennung: Verhindert doppelte Cluster bei gleichem Namen + - Scans werden korrekt dem existierenden oder neuen Cluster zugeordnet + - Parent-Referenzen werden korrekt gesetzt (Cluster→Registration, Scan→Cluster) + - Detailliertes Logging aller Merge-Operationen + - Finale Scan/Cluster-Statistik nach Merge + +### Verbessert +- LSDX-Struktur im Code dokumentiert +- Verbesserte Fehlerbehandlung beim Merge + +--- + +## [v4.0] - 2026-01-10 + +### Hinzugefügt +- **Projekt Merger**: Neuer Modus zum Zusammenführen mehrerer PointCab-Projekte + - Einzelprojekt-Merge: Ein Quellprojekt → Zielprojekt + - Batch-Merge: Mehrere Quellprojekte → Zielprojekt + - Intelligente Konfliktauflösung mit `_merged_N` Suffix + - Vollständige LSDX-Zusammenführung (Cluster, Scans, Dateireferenzen) + - UUID-Regenerierung für alle übertragenen Elemente + - Automatisches Backup der Ziel-LSDX vor dem Merge + +### Verbessert +- Neue GUI für den Merger mit Konfliktvorschau +- Batch-Merge mit Fortschrittsanzeige + +--- + +## [v3.1] - 2026-01-05 + +### Geändert +- **Neues Namensformat**: `[ClusterName]_[ScanName].[Erweiterung]` + - Vorher: `[ClusterName].[Erweiterung]` + - Nachher: `EG_Flur_scan001.lsd` +- Scan-Namen werden aus der LSDX extrahiert +- Cluster-Nummer-Duplikate werden vermieden + +--- + +## [v3.0] - 2025-12-20 + +### Hinzugefügt +- **Batch-Verarbeitung**: Mehrere Projekte gleichzeitig umbenennen + - Automatische Projekterkennung im Basisordner + - Selektive Projektauswahl + - Fortschrittsanzeige für Batch-Operationen + - Zusammenfassendes Batch-Log + +### Verbessert +- GUI-Umstrukturierung mit Hauptmenü +- Verbesserte Fehlerbehandlung bei Dateioperationen + +--- + +## [v2.0] - 2025-12-01 + +### Hinzugefügt +- **Cluster-Bereinigung**: Automatische Entfernung von Suffixen + - Konfigurierbar über `cluster_cleanup.txt` + - Entfernt `_re`, `_li`, `_mi`, etc. +- Button "Konfiguration neu laden" + +### Verbessert +- Verbesserte Vorschau der Umbenennung +- Detaillierteres Logging + +--- + +## [v1.0] - 2025-11-15 + +### Erstveröffentlichung +- Grundfunktion: LSDX-Dateien einlesen +- Scans aus PointCloud-Ordner umbenennen +- Grafische Benutzeroberfläche (tkinter) +- Vorschau vor Umbenennung +- Log-Datei-Erstellung + +--- + +## Geplante Features + +- [ ] Kommandozeilen-Unterstützung (CLI-Modus) +- [ ] Rückgängig-Funktion für Umbenennungen +- [ ] Automatische Updates +- [ ] Mehrsprachige Unterstützung (Englisch) + +--- + +*Hinweis: Dieses Changelog folgt dem Format von [Keep a Changelog](https://keepachangelog.com/).* diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..b31827a --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,592 @@ +# PointCab Renamer - Deployment-Anleitung + +**Version 4.2.1** | Datum: 16. Januar 2026 + +--- + +## Build-Status (Testergebnisse 2026-01-16) + +| Build-Methode | Status | Hinweise | +|---------------|--------|----------| +| `build_windows.bat` | ✅ Funktioniert | Empfohlen auf Windows | +| `build_linux.sh` | ✅ Getestet | Funktioniert auf Ubuntu 20.04+ | +| `build_windows_wine.sh` | ⚠️ Experimentell | Fehlschläge auf Headless-Servern möglich | +| `build_windows_on_linux.sh` | ⚠️ Docker | Nicht in Docker-in-Docker möglich | + +--- + +## Übersicht + +Diese Anleitung beschreibt, wie Sie aus dem Python-Quellcode ausführbare Dateien für Windows (.exe) und Ubuntu (Binary) erstellen. + +--- + +## Voraussetzungen + +### Benötigte Software + +| Komponente | Windows | Ubuntu | +|------------|---------|--------| +| Python | 3.8+ | 3.8+ | +| PyInstaller | 5.0+ | 5.0+ | +| tkinter | (in Python enthalten) | `python3-tk` | + +### Installation der Voraussetzungen + +#### Windows + +1. **Python installieren:** + - Laden Sie Python von https://www.python.org/downloads/ herunter + - Bei der Installation: ☑ "Add Python to PATH" aktivieren + +2. **PyInstaller installieren:** + ```cmd + pip install pyinstaller + ``` + +#### Ubuntu + +1. **Python und tkinter installieren:** + ```bash + sudo apt update + sudo apt install python3 python3-pip python3-tk + ``` + +2. **PyInstaller installieren:** + ```bash + pip3 install pyinstaller + ``` + +--- + +## Windows-Build (.exe) + +### Automatisch (empfohlen) + +1. Öffnen Sie eine Eingabeaufforderung (cmd) +2. Navigieren Sie zum Projektordner: + ```cmd + cd C:\Pfad\zum\pointcab_renamer + ``` +3. Führen Sie das Build-Skript aus: + ```cmd + build_windows.bat + ``` +4. Die fertige `.exe` finden Sie im Ordner `dist\pointcab_renamer\` + +### Manuell + +1. Öffnen Sie eine Eingabeaufforderung +2. Navigieren Sie zum Quellcode-Ordner +3. Führen Sie PyInstaller aus: + ```cmd + pyinstaller --onefile --windowed --name "PointCab_Renamer" ^
--add-data "cluster_cleanup.txt;." ^
pointcab_renamer.py + ``` +4. Die `.exe` befindet sich in `dist\PointCab_Renamer.exe` + +### PyInstaller-Optionen erklärt + +| Option | Beschreibung | +|--------|-------------| +| `--onefile` | Alles in eine einzige .exe packen | +| `--windowed` | Kein Konsolenfenster anzeigen | +| `--name` | Name der Ausgabedatei | +| `--add-data` | Zusätzliche Dateien einbinden | +| `--icon` | (Optional) Icon-Datei (.ico) | + +### Bekannte Probleme unter Windows + +**Problem:** Antivirus blockiert die .exe + +**Lösung:** Die erstellte .exe als Ausnahme hinzufügen oder signieren. + +**Problem:** "DLL nicht gefunden" + +**Lösung:** Visual C++ Redistributable installieren. + +--- + +## Ubuntu-Build (Binary) + +### Automatisch (empfohlen) + +1. Öffnen Sie ein Terminal +2. Navigieren Sie zum Projektordner: + ```bash + cd /pfad/zum/pointcab_renamer + ``` +3. Machen Sie das Build-Skript ausführbar und führen Sie es aus: + ```bash + chmod +x build_linux.sh + ./build_linux.sh + ``` +4. Das fertige Binary finden Sie im Ordner `dist/` + +### Manuell + +1. Öffnen Sie ein Terminal +2. Navigieren Sie zum Quellcode-Ordner +3. Führen Sie PyInstaller aus: + ```bash + pyinstaller --onefile --name "pointcab_renamer" \ + --add-data "cluster_cleanup.txt:." \ + pointcab_renamer.py + ``` +4. Das Binary befindet sich in `dist/pointcab_renamer` +5. Machen Sie es ausführbar: + ```bash + chmod +x dist/pointcab_renamer + ``` + +### Bekannte Probleme unter Ubuntu + +**Problem:** "No display name and no $DISPLAY environment variable" + +**Lösung:** Das Binary muss in einer grafischen Umgebung gestartet werden, nicht über SSH. + +**Problem:** "_tkinter not found" + +**Lösung:** `sudo apt install python3-tk` + +--- + +## Cross-Compilation: Windows .exe unter Linux erstellen + +Es gibt mehrere Möglichkeiten, eine Windows .exe unter Linux zu erstellen, ohne Windows zu installieren. + +### Methode 1: Docker (Empfohlen) + +Die Docker-Methode ist die zuverlässigste und reproduzierbarste Option. + +#### Voraussetzungen + +1. **Docker installieren:** + ```bash + sudo apt update + sudo apt install docker.io + sudo systemctl start docker + sudo systemctl enable docker + ``` + +2. **Benutzer zur docker-Gruppe hinzufügen:** + ```bash + sudo usermod -aG docker $USER + # Danach neu einloggen oder: + newgrp docker + ``` + +3. **Docker-Installation testen:** + ```bash + docker run hello-world + ``` + +#### Verwendung + +1. Navigieren Sie zum Projektordner: + ```bash + cd /pfad/zum/pointcab_renamer + ``` + +2. Führen Sie das Build-Skript aus: + ```bash + ./build_windows_on_linux.sh + ``` + +3. Die fertige `.exe` befindet sich in `dist/PointCab_Renamer.exe` + +#### Was das Skript macht + +1. Prüft Docker-Installation und -Status +2. Lädt das `cdrx/pyinstaller-windows` Docker-Image (beim ersten Mal) +3. Startet einen Container mit Windows-Umgebung +4. Führt PyInstaller im Container aus +5. Kopiert die .exe und Zusatzdateien nach `dist/` + +#### Vorteile der Docker-Methode + +- ✅ Zuverlässig und reproduzierbar +- ✅ Isolierte Build-Umgebung +- ✅ Keine manuelle Windows-Python-Installation +- ✅ Gleiche Ergebnisse wie auf echtem Windows +- ✅ Einfach in CI/CD-Pipelines integrierbar + +#### Troubleshooting Docker + +**Problem:** "Permission denied" beim Docker-Aufruf + +**Lösung:** +```bash +sudo usermod -aG docker $USER +# Neu einloggen erforderlich! +``` + +**Problem:** Docker-Image-Download schlägt fehl + +**Lösung:** Proxy-Einstellungen prüfen oder manuell herunterladen: +```bash +docker pull cdrx/pyinstaller-windows:python3 +``` + +**Problem:** Container startet nicht + +**Lösung:** Docker-Daemon prüfen: +```bash +sudo systemctl status docker +sudo systemctl restart docker +``` + +--- + +### Methode 2: Wine (Fallback) + +Die Wine-Methode ist weniger zuverlässig, kann aber ohne Docker verwendet werden. + +#### Voraussetzungen + +1. **Wine installieren:** + ```bash + sudo dpkg --add-architecture i386 + sudo apt update + sudo apt install wine64 wine32 + ``` + +2. **Installation prüfen:** + ```bash + wine --version + ``` + +#### Verwendung + +1. Navigieren Sie zum Projektordner: + ```bash + cd /pfad/zum/pointcab_renamer + ``` + +2. Führen Sie das Build-Skript aus: + ```bash + ./build_windows_wine.sh + ``` + +3. Das Skript installiert automatisch: + - Windows-Python in Wine + - PyInstaller + +#### Einschränkungen der Wine-Methode + +- ⚠️ Nicht alle Windows-Funktionen werden unterstützt +- ⚠️ Kann bei komplexen Abhängigkeiten fehlschlagen +- ⚠️ Langsamerer Build-Prozess +- ⚠️ Ergebnisse können von echter Windows-Build abweichen + +--- + +### Methode 3: GitHub Actions (Automatisiert) + +Für regelmäßige Builds können Sie GitHub Actions verwenden. + +Erstellen Sie `.github/workflows/build.yml`: + +```yaml +name: Build Windows Executable + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + build-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: pip install pyinstaller + + - name: Build executable + run: | + pyinstaller --onefile --windowed --name "PointCab_Renamer" ` + --add-data "cluster_cleanup.txt;." ` + --add-data "BENUTZERHANDBUCH.md;." ` + pointcab_renamer.py + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: PointCab_Renamer_Windows + path: | + dist/PointCab_Renamer.exe + cluster_cleanup.txt + BENUTZERHANDBUCH.md +``` + +--- + +### Vergleich der Cross-Compilation-Methoden + +| Methode | Zuverlässigkeit | Geschwindigkeit | Aufwand | +|---------|-----------------|-----------------|---------| +| Docker | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | Niedrig | +| Wine | ⭐⭐ | ⭐⭐ | Mittel | +| GitHub Actions | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Niedrig | +| Echtes Windows | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Hoch (VM) | + +**Empfehlung:** Verwenden Sie die Docker-Methode für lokale Builds und GitHub Actions für automatisierte Release-Builds. + +--- + +## Testen der Executables + +### Windows-Test + +1. Kopieren Sie die `.exe` und `cluster_cleanup.txt` in einen Testordner +2. Doppelklicken Sie auf die `.exe` +3. Das Hauptmenü sollte erscheinen +4. Testen Sie alle drei Modi mit einem Testprojekt + +### Ubuntu-Test + +1. Kopieren Sie das Binary und `cluster_cleanup.txt` in einen Testordner +2. Starten Sie das Programm: + ```bash + ./pointcab_renamer + ``` +3. Das Hauptmenü sollte erscheinen +4. Testen Sie alle drei Modi mit einem Testprojekt + +### Checkliste für Tests + +- [ ] Programm startet ohne Fehler +- [ ] Hauptmenü wird angezeigt +- [ ] LSDX-Datei kann ausgewählt werden +- [ ] PointCloud-Ordner wird erkannt +- [ ] Vorschau wird korrekt angezeigt +- [ ] Umbenennung funktioniert +- [ ] LSDX wird aktualisiert +- [ ] Log-Datei wird erstellt +- [ ] Batch-Modus funktioniert +- [ ] Merger funktioniert + +--- + +## Distribution an Mitarbeiter + +### Bereitstellung + +1. **Für Windows:** + - Kopieren Sie diese Dateien in einen Ordner: + - `PointCab_Renamer.exe` + - `cluster_cleanup.txt` + - `BENUTZERHANDBUCH.md` (oder als PDF) + - Erstellen Sie ein ZIP-Archiv + - Verteilen Sie über Netzlaufwerk oder E-Mail + +2. **Für Ubuntu:** + - Kopieren Sie diese Dateien in einen Ordner: + - `pointcab_renamer` + - `cluster_cleanup.txt` + - `BENUTZERHANDBUCH.md` + - Erstellen Sie ein tar.gz-Archiv: + ```bash + tar -czvf pointcab_renamer_linux.tar.gz pointcab_renamer cluster_cleanup.txt BENUTZERHANDBUCH.md + ``` + - Verteilen Sie über Netzlaufwerk + +### Empfohlene Ordnerstruktur für Mitarbeiter + +``` +PointCab_Renamer/ +├── PointCab_Renamer.exe (oder pointcab_renamer für Linux) +├── cluster_cleanup.txt +├── BENUTZERHANDBUCH.md +└── logs/ (wird automatisch erstellt) +``` + +### Updates verteilen + +1. Erstellen Sie die neue Executable +2. Informieren Sie die Mitarbeiter über Änderungen (CHANGELOG) +3. Mitarbeiter ersetzen die alte .exe durch die neue +4. `cluster_cleanup.txt` kann beibehalten werden (falls angepasst) + +--- + +## Troubleshooting beim Build + +### "ModuleNotFoundError" + +**Lösung:** Fehlende Module installieren: +```bash +pip install +``` + +### "Hidden import not found" + +**Lösung:** Hidden imports hinzufügen: +```bash +pyinstaller --hidden-import= ... +``` + +### "Executable zu groß" (>100MB) + +**Lösung:** UPX-Kompression aktivieren: +```bash +pip install upx +pyinstaller --onefile --upx-dir=/pfad/zu/upx ... +``` + +### "tkinter funktioniert nicht" + +**Windows:** tkinter ist normalerweise in Python enthalten. Reinstallieren Sie Python mit der "tcl/tk" Option. + +**Ubuntu:** Installieren Sie python3-tk: +```bash +sudo apt install python3-tk +``` + +--- + +## Versionskontrolle + +Bei jeder neuen Version: + +1. Version im Quellcode aktualisieren (`VERSION = "4.2"` etc.) +2. CHANGELOG.md aktualisieren +3. Neue Builds für Windows und Ubuntu erstellen +4. Builds testen +5. Im Git-Repository taggen: + ```bash + git tag -a v4.2 -m "Version 4.2" + git push origin v4.2 + ``` + +--- + +--- + +## Troubleshooting: Docker-Probleme + +### Docker ist nicht installiert + +**Ubuntu/Debian:** +```bash +sudo apt update +sudo apt install docker.io +sudo systemctl start docker +sudo systemctl enable docker +sudo usermod -aG docker $USER +# Dann neu einloggen oder: newgrp docker +``` + +**Fedora/RHEL:** +```bash +sudo dnf install docker +sudo systemctl start docker +``` + +### Docker-Daemon startet nicht + +**Symptom:** `Cannot connect to the Docker daemon` + +**Lösungen:** + +1. **Service starten:** + ```bash + sudo systemctl start docker + ``` + +2. **Status prüfen:** + ```bash + sudo systemctl status docker + ``` + +3. **Logs prüfen:** + ```bash + sudo journalctl -u docker.service + ``` + +### Berechtigung verweigert + +**Symptom:** `permission denied while trying to connect to the Docker daemon` + +**Lösung:** Benutzer zur Docker-Gruppe hinzufügen: +```bash +sudo usermod -aG docker $USER +# Danach neu einloggen +``` + +Oder mit sudo ausführen: +```bash +sudo ./build_windows_on_linux.sh +``` + +### Docker in Container-Umgebung (Docker-in-Docker) + +**Problem:** Docker kann nicht in unprivilegierten Containern laufen. + +**Lösungen:** + +1. **Wine-Alternative verwenden:** + ```bash + ./build_windows_wine.sh + ``` + +2. **Auf Host-System bauen** + +3. **GitHub Actions nutzen** (siehe `.github/workflows/`) + +4. **Container mit `--privileged` starten** (nicht empfohlen für Produktion) + +### WSL2 unter Windows + +**Problem:** Docker-Befehle schlagen in WSL2 fehl. + +**Lösung:** +1. Docker Desktop für Windows installieren +2. In Docker Desktop: Settings → Resources → WSL Integration aktivieren +3. WSL-Distribution auswählen + +### Docker-Image Download schlägt fehl + +**Symptom:** `Error pulling image` oder Timeout + +**Lösungen:** + +1. **Internetverbindung prüfen** + +2. **Proxy konfigurieren:** + ```bash + export HTTP_PROXY=http://proxy:port + export HTTPS_PROXY=http://proxy:port + ``` + +3. **Manueller Download:** + ```bash + sudo docker pull cdrx/pyinstaller-windows:python3 + ``` + +--- + +## Vergleich der Build-Methoden + +| Methode | Plattform | Vorteile | Nachteile | +|---------|-----------|----------|-----------| +| `build_windows.bat` | Windows | Nativ, zuverlässig | Braucht Windows | +| `build_windows_on_linux.sh` | Linux + Docker | Cross-compilation | Docker erforderlich | +| `build_windows_wine.sh` | Linux + Wine | Kein Docker nötig | Weniger zuverlässig | +| GitHub Actions | Cloud | Automatisiert | Braucht GitHub-Repo | + +**Empfehlung:** Für zuverlässige Windows-Builds verwenden Sie: +1. **Native Windows** (build_windows.bat) - Am zuverlässigsten +2. **Docker auf Linux** (build_windows_on_linux.sh) - Gut für CI/CD +3. **GitHub Actions** - Automatisiert bei jedem Push/Release + +--- + +*PointCab Renamer Deployment Guide v4.1.2 - © 2026* diff --git a/DEPLOYMENT.pdf b/DEPLOYMENT.pdf new file mode 100644 index 0000000..507bbfb Binary files /dev/null and b/DEPLOYMENT.pdf differ diff --git a/GIT_SETUP.md b/GIT_SETUP.md new file mode 100644 index 0000000..ba79337 --- /dev/null +++ b/GIT_SETUP.md @@ -0,0 +1,107 @@ +# Git-Repository Setup für PointCab Renamer + +## Voraussetzungen + +- Git installiert +- Zugang zu Gitea/GitHub Repository + +## Lokales Repository initialisieren + +Das Repository wurde bereits initialisiert. Falls Sie ein neues Repository erstellen möchten: + +```bash +cd pointcab_renamer +git init +git add . +git commit -m "Initial commit: PointCab Renamer v4.2" +``` + +## Zu Gitea pushen + +1. **Repository auf Gitea erstellen** (falls noch nicht geschehen) + - Loggen Sie sich bei Gitea ein + - Erstellen Sie ein neues Repository (z.B. `pointcab_renamer`) + - Kopieren Sie die Repository-URL + +2. **Remote hinzufügen und pushen:** + +```bash +# Remote hinzufügen +git remote add origin https://gitea.example.com/username/pointcab_renamer.git + +# Oder für SSH: +git remote add origin git@gitea.example.com:username/pointcab_renamer.git + +# Push zum Remote +git push -u origin main +``` + +## Zu GitHub pushen + +```bash +# Remote hinzufügen +git remote add origin https://github.com/username/pointcab_renamer.git + +# Oder für SSH: +git remote add origin git@github.com:username/pointcab_renamer.git + +# Push zum Remote +git push -u origin main +``` + +## Änderungen pushen + +Nach dem initialen Push: + +```bash +# Änderungen hinzufügen +git add . + +# Commit erstellen +git commit -m "Beschreibung der Änderungen" + +# Pushen +git push +``` + +## Branching-Strategie + +- `main` - Stabiler Release-Branch +- `develop` - Entwicklungs-Branch +- `feature/*` - Feature-Branches +- `bugfix/*` - Bugfix-Branches + +## Releases erstellen + +```bash +# Tag für Release erstellen +git tag -a v4.2 -m "Release v4.2 - Bugfixes und Parser-Verbesserungen" + +# Tag pushen +git push origin v4.2 +``` + +## Häufige Befehle + +```bash +# Status anzeigen +git status + +# Log anzeigen +git log --oneline + +# Änderungen abrufen +git pull + +# Branch wechseln +git checkout branch-name + +# Neuen Branch erstellen +git checkout -b neuer-branch +``` + +## Hinweise + +- Die `.gitignore` ignoriert Build-Artefakte, Logs und temporäre Dateien +- Bei Konflikten: `git pull --rebase` verwenden +- Regelmäßig pushen, um Datenverlust zu vermeiden diff --git a/INSTALLATION.txt b/INSTALLATION.txt new file mode 100644 index 0000000..86943d7 --- /dev/null +++ b/INSTALLATION.txt @@ -0,0 +1,73 @@ +===================================================== + PointCab Renamer v4.1 - Schnellstart-Anleitung +===================================================== + +INHALT DES ARCHIVS: +------------------- +- pointcab_renamer.py - Hauptprogramm (Quellcode) +- cluster_cleanup.txt - Konfigurationsdatei +- BENUTZERHANDBUCH.md - Ausführliche Anleitung +- DEPLOYMENT.md - Anleitung zum Erstellen von .exe/Binary +- README.md - Projektübersicht +- CHANGELOG.md - Versionsänderungen +- build_windows.bat - Build-Skript für Windows +- build_linux.sh - Build-Skript für Linux +- requirements.txt - Python-Abhängigkeiten +- LICENSE.txt - Lizenzinformationen +- VERSION.txt - Versionsinformationen + + +SCHNELLSTART - WINDOWS: +----------------------- +1. Entpacken Sie das Archiv in einen beliebigen Ordner +2. Option A - Mit Python: + - Python 3.8+ installieren (python.org) + - Doppelklick auf pointcab_renamer.py + + Option B - Als .exe erstellen: + - Doppelklick auf build_windows.bat + - Fertige .exe liegt in dist/ + + +SCHNELLSTART - LINUX/UBUNTU: +---------------------------- +1. Entpacken Sie das Archiv: + unzip pointcab_renamer_v4.1_release.zip + cd pointcab_renamer_release + +2. Option A - Mit Python: + sudo apt install python3 python3-tk + python3 pointcab_renamer.py + + Option B - Als Binary erstellen: + chmod +x build_linux.sh + ./build_linux.sh + ./dist/pointcab_renamer + + +ERSTE SCHRITTE: +--------------- +1. Starten Sie das Programm +2. Wählen Sie einen Modus: + - Einzelprojekt: Ein PointCab-Projekt umbenennen + - Batch: Mehrere Projekte auf einmal + - Merger: Projekte zusammenführen +3. Folgen Sie den Anweisungen auf dem Bildschirm + + +WICHTIGE HINWEISE: +------------------ +- Die Datei cluster_cleanup.txt muss im selben Ordner + wie das Programm liegen +- Vor dem Umbenennen immer ein Backup erstellen! +- Bei Problemen: BENUTZERHANDBUCH.md lesen + + +SUPPORT: +-------- +Bei Fragen wenden Sie sich an die IT-Abteilung. + + +===================================================== + Version 4.1 | Januar 2026 +===================================================== diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f588611 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,32 @@ +PointCab Renamer - Lizenzvereinbarung +===================================== + +Copyright (c) 2026 - Alle Rechte vorbehalten + +NUTZUNGSBEDINGUNGEN: + +1. INTERNE NUTZUNG + Diese Software ist ausschließlich für den internen Gebrauch + innerhalb des Unternehmens bestimmt. + +2. WEITERGABE + Die Weitergabe an Dritte außerhalb des Unternehmens ist + ohne ausdrückliche schriftliche Genehmigung untersagt. + +3. VERÄNDERUNGEN + Änderungen am Quellcode sind nur mit Rücksprache mit der + IT-Abteilung gestattet. + +4. GEWÄHRLEISTUNG + Die Software wird "wie besehen" ohne jegliche Gewährleistung + bereitgestellt. Der Autor haftet nicht für Schäden, die durch + die Nutzung dieser Software entstehen könnten. + +5. SUPPORT + Bei Fragen oder Problemen wenden Sie sich bitte an die + IT-Abteilung. + +--- + +Diese Lizenz gilt für alle Versionen der Software, sofern +nicht anders angegeben. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1af9ffe --- /dev/null +++ b/README.md @@ -0,0 +1,276 @@ +# PointCab Projekt Umbenenner v4.1.1 + +Ein GUI-Tool zum Umbenennen von Scans in PointCab-Projekten und zum Zusammenführen mehrerer Projekte. + +## Funktionen + +### 1. 📁 Einzelprojekt bearbeiten +- Einzelnes PointCab-Projekt auswählen und Scans umbenennen +- Vollständige Scan-Namen: `1.lsd → Projektname_01.lsd` +- Clustername-Bereinigung über Konfigurationsdatei +- Automatisches Backup der LSDX-Datei + +### 2. 📂 Batch Renamer +- Mehrere Projekte in einem Verzeichnis automatisch verarbeiten +- Fortschrittsanzeige und detailliertes Logging +- Fehlertoleranz: Bei Fehler wird mit nächstem Projekt fortgefahren + +### 3. 🔀 Projektmerger (verbessert in v4.1) +- Mehrere PointCab-Projekte in ein Stammprojekt zusammenführen +- Zwei Modi: Einzelprojekt oder Batch-Merge +- **NEU**: Intelligente Cluster-Duplikat-Erkennung +- Intelligente Namenskonflikt-Behandlung +- Vollständige LSDX-Zusammenführung mit detailliertem Logging + +## Installation + +### Voraussetzungen +- Python 3.8 oder höher +- Tkinter (normalerweise in Python enthalten) + +### Ausführen +```bash +python pointcab_renamer.py +``` + +## Projektmerger - Detaillierte Dokumentation + +### Konzept +Der Projektmerger ermöglicht das Zusammenführen mehrerer PointCab-Projekte in ein einzelnes Stammprojekt. Dies ist nützlich wenn: +- Mehrere Scan-Sessions zu einem Projekt gehören +- Projekte nachträglich zusammengeführt werden sollen +- Daten aus verschiedenen Quellen konsolidiert werden müssen + +### Modi + +#### Einzelprojekt hinzufügen +1. Stammprojekt (Ziel) auswählen +2. Ein einzelnes Quellprojekt auswählen +3. Vorschau anzeigen +4. Merge durchführen + +#### Batch-Merge +1. Stammprojekt (Ziel) auswählen +2. Hauptverzeichnis mit mehreren Quellprojekten auswählen +3. Alle gefundenen Projekte werden automatisch erkannt +4. Vorschau anzeigen +5. Merge durchführen + +### Merge-Operationen + +Der Projektmerger führt folgende Operationen durch: + +1. **Backup erstellen** + - Vor dem Merge wird ein Backup der Stammprojekt-LSDX erstellt + - Format: `projektname.lsdx.backup_YYYYMMDD_HHMMSS` + +2. **LSD-Dateien kopieren** + - Alle LSD-Dateien aus den Quellprojekten werden in das Stammprojekt kopiert + - Bei Namenskonflikten: Automatische Umbenennung (siehe unten) + +3. **PNG-Dateien kopieren** + - Alle Preview-Bilder werden in den Previews-Ordner des Stammprojekts kopiert + - Bei Namenskonflikten: Automatische Umbenennung + +4. **LSDX zusammenführen** + - **Cluster-Duplikat-Erkennung** (NEU in v4.1): + - Prüft ob Cluster mit gleichem Namen bereits existiert + - Bei Duplikat: Scans werden dem existierenden Cluster zugeordnet + - Bei neuem Cluster: Neuer Cluster wird mit neuer UUID hinzugefügt + - Alle Scan-Elemente werden mit korrekten Parent-Referenzen eingefügt + - UUIDs werden neu generiert um Konflikte zu vermeiden + - FilePath-Referenzen werden bei Umbenennung angepasst + - Detailliertes Logging aller Operationen + +### Namenskonflikt-Behandlung + +Wenn eine Datei im Zielordner bereits existiert: + +``` +Vor Merge: + Stammprojekt/PointCloud/scan_01.lsd (existiert) + Quellprojekt/PointCloud/scan_01.lsd (zu mergen) + +Nach Merge: + Stammprojekt/PointCloud/scan_01.lsd (original) + Stammprojekt/PointCloud/scan_01_merged_1.lsd (aus Quellprojekt) +``` + +Die LSDX-Referenzen werden automatisch aktualisiert: +```xml + +scan_01.lsd + + +scan_01_merged_1.lsd +``` + +### Beispiel: Einzelprojekt-Merge + +``` +Vorher: +├── Stammprojekt/ +│ ├── Stammprojekt_PointCloud/ +│ │ ├── Stammprojekt.lsdx +│ │ ├── 1.lsd +│ │ ├── 2.lsd +│ │ └── Previews/ +│ │ ├── 1.png +│ │ └── 2.png + +├── Quellprojekt/ +│ ├── Quellprojekt_PointCloud/ +│ │ ├── Quellprojekt.lsdx +│ │ ├── 1.lsd +│ │ ├── 2.lsd +│ │ └── Previews/ +│ │ ├── 1.png +│ │ └── 2.png + +Nachher: +├── Stammprojekt/ +│ ├── Stammprojekt_PointCloud/ +│ │ ├── Stammprojekt.lsdx (zusammengeführt) +│ │ ├── Stammprojekt.lsdx.backup_20260114_101500 +│ │ ├── 1.lsd +│ │ ├── 2.lsd +│ │ ├── 1_merged_1.lsd (aus Quellprojekt) +│ │ ├── 2_merged_2.lsd (aus Quellprojekt) +│ │ └── Previews/ +│ │ ├── 1.png +│ │ ├── 2.png +│ │ ├── 1_merged_1.png +│ │ └── 2_merged_2.png +│ └── merge_20260114_101500.log +``` + +### Beispiel: Batch-Merge + +``` +Vorher: +├── Hauptverzeichnis/ +│ ├── Stammprojekt/ +│ │ └── Stammprojekt_PointCloud/ +│ │ ├── Stammprojekt.lsdx +│ │ └── (Scans 1-5) +│ ├── Projekt_A/ +│ │ └── Projekt_A_PointCloud/ +│ │ ├── Projekt_A.lsdx +│ │ └── (Scans 1-3) +│ └── Projekt_B/ +│ └── Projekt_B_PointCloud/ +│ ├── Projekt_B.lsdx +│ └── (Scans 1-4) + +Nach Batch-Merge (Stammprojekt als Ziel, Hauptverzeichnis als Quelle): +├── Stammprojekt/ +│ └── Stammprojekt_PointCloud/ +│ ├── Stammprojekt.lsdx (enthält jetzt 12 Scans) +│ ├── Stammprojekt.lsdx.backup_... +│ └── (alle LSD/PNG-Dateien) +│ └── merge_....log +``` + +### Logging + +Jeder Merge-Vorgang erstellt eine detaillierte Log-Datei: + +- **Einzelprojekt-Merge**: `merge_YYYYMMDD_HHMMSS.log` im Stammprojekt-Verzeichnis +- **Batch-Merge**: Eine Log-Datei pro Merge-Vorgang + +Log-Inhalt: +- Alle kopierten Dateien +- Umbenennungen bei Konflikten +- Aktualisierte LSDX-Referenzen +- Fehler und Warnungen + +### Fehlerbehandlung + +- **Fehlende Dateien**: Werden übersprungen, Warnung im Log +- **Batch-Merge bei Fehler**: Verarbeitung wird mit nächstem Projekt fortgesetzt +- **LSDX-Parsing-Fehler**: Projekt wird übersprungen +- **Backup**: Immer vor Änderungen erstellt + +## Konfiguration + +### cluster_cleanup.txt +Strings die aus dem Clusternamen entfernt werden: +``` +_re +_li +_mi +# Kommentare mit # beginnen +``` + +## Executable erstellen + +### Windows (auf Windows) +```bash +pip install pyinstaller +pyinstaller --onefile --windowed pointcab_renamer.py +``` + +### Linux +```bash +./build_linux.sh +``` + +### Cross-Compilation: Windows .exe unter Linux +Es ist möglich, eine Windows .exe unter Linux zu erstellen. Dazu stehen zwei Methoden zur Verfügung: + +```bash +# Methode 1: Docker (empfohlen) +./build_windows_on_linux.sh + +# Methode 2: Wine (Fallback) +./build_windows_wine.sh +``` + +Die Docker-Methode ist zuverlässiger und wird empfohlen. Für Details siehe [DEPLOYMENT.md](DEPLOYMENT.md). + +**Wichtig:** Die `cluster_cleanup.txt` muss neben der .exe-Datei liegen. + +## Changelog + +### v4.1.1 (2026-01-14) +- **NEU**: Cross-Compilation-Unterstützung für Windows .exe unter Linux + - Docker-basiertes Build-Skript (`build_windows_on_linux.sh`) + - Wine-basiertes Fallback-Skript (`build_windows_wine.sh`) + - GitHub Actions Beispiel-Workflow +- Erweiterte DEPLOYMENT.md-Dokumentation + +### v4.1 (2026-01-14) +- **FIX**: Projektmerger LSDX-Zusammenführung komplett überarbeitet + - Cluster-Duplikat-Erkennung: Verhindert doppelte Cluster bei gleichem Namen + - Scans werden korrekt dem existierenden oder neuen Cluster zugeordnet + - Parent-Referenzen werden jetzt korrekt gesetzt (Cluster→Registration, Scan→Cluster) + - Detailliertes Logging aller Merge-Operationen + - Finale Scan/Cluster-Statistik nach Merge +- LSDX-Struktur im Code dokumentiert + +### v4.0 (2026-01-14) +- **NEU**: Projektmerger-Funktion + - Einzelprojekt- und Batch-Merge-Modi + - Intelligente Namenskonflikt-Behandlung + - Automatische UUID-Neugenerierung + - Vollständige LSDX-Zusammenführung +- Hauptmenü auf 3 Optionen erweitert +- Verbesserte Fehlerbehandlung + +### v3.1 (2026-01-14) +- Full-Scan-Name-Pattern: `1.lsd → Projektname_01.lsd` +- Konsistente Benennung in Dateien und LSDX + +### v3.0 (2026-01-14) +- Batch Renamer hinzugefügt +- Hauptmenü für Modus-Auswahl + +### v2.0 (2026-01-14) +- Clustername-Bereinigung via Konfigurationsdatei + +### v1.0 (2026-01-14) +- Initiale Version + +## Lizenz + +MIT License diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 0000000..fae6e3d --- /dev/null +++ b/VERSION.txt @@ -0,0 +1 @@ +4.2.1 diff --git a/build/pointcab_renamer/Analysis-00.toc b/build/pointcab_renamer/Analysis-00.toc new file mode 100644 index 0000000..68c5653 --- /dev/null +++ b/build/pointcab_renamer/Analysis-00.toc @@ -0,0 +1,1778 @@ +(['/home/ubuntu/pointcab_renamer/pointcab_renamer.py'], + ['/home/ubuntu/pointcab_renamer'], + [], + [('/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/site-packages/lark/__pyinstaller', + 0), + ('/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/site-packages/patchright/_impl/__pyinstaller', + 0), + ('/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/site-packages/numpy/_pyinstaller', + 0), + ('/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/site-packages/playwright/_impl/__pyinstaller', + 0), + ('/home/ubuntu/.local/lib/python3.11/site-packages/_pyinstaller_hooks_contrib/stdhooks', + -1000), + ('/home/ubuntu/.local/lib/python3.11/site-packages/_pyinstaller_hooks_contrib', + -1000)], + {}, + [], + [], + False, + {}, + 0, + [], + [('cluster_cleanup.txt', + '/home/ubuntu/pointcab_renamer/cluster_cleanup.txt', + 'DATA')], + '3.11.6 (main, Jan 6 2026, 05:54:35) [GCC 12.2.0]', + [('pyi_rth_inspect', + '/home/ubuntu/.local/lib/python3.11/site-packages/PyInstaller/hooks/rthooks/pyi_rth_inspect.py', + 'PYSOURCE'), + ('pyi_rth__tkinter', + '/home/ubuntu/.local/lib/python3.11/site-packages/PyInstaller/hooks/rthooks/pyi_rth__tkinter.py', + 'PYSOURCE'), + ('pointcab_renamer', + '/home/ubuntu/pointcab_renamer/pointcab_renamer.py', + 'PYSOURCE')], + [('zipfile', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/zipfile.py', + 'PYMODULE'), + ('argparse', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/argparse.py', + 'PYMODULE'), + ('textwrap', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/textwrap.py', + 'PYMODULE'), + ('gettext', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/gettext.py', + 'PYMODULE'), + ('py_compile', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/py_compile.py', + 'PYMODULE'), + ('importlib.machinery', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/machinery.py', + 'PYMODULE'), + ('importlib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/__init__.py', + 'PYMODULE'), + ('importlib._bootstrap', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/_bootstrap.py', + 'PYMODULE'), + ('importlib._bootstrap_external', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/_bootstrap_external.py', + 'PYMODULE'), + ('importlib.metadata', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/__init__.py', + 'PYMODULE'), + ('typing', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/typing.py', + 'PYMODULE'), + ('importlib.abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/abc.py', + 'PYMODULE'), + ('importlib.resources.abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/abc.py', + 'PYMODULE'), + ('importlib.resources', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/__init__.py', + 'PYMODULE'), + ('importlib.resources._legacy', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/_legacy.py', + 'PYMODULE'), + ('importlib.resources._common', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/_common.py', + 'PYMODULE'), + ('importlib.resources._adapters', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/_adapters.py', + 'PYMODULE'), + ('tempfile', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tempfile.py', + 'PYMODULE'), + ('random', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/random.py', + 'PYMODULE'), + ('statistics', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/statistics.py', + 'PYMODULE'), + ('decimal', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/decimal.py', + 'PYMODULE'), + ('_pydecimal', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_pydecimal.py', + 'PYMODULE'), + ('contextvars', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/contextvars.py', + 'PYMODULE'), + ('fractions', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/fractions.py', + 'PYMODULE'), + ('numbers', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/numbers.py', + 'PYMODULE'), + ('hashlib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/hashlib.py', + 'PYMODULE'), + ('bisect', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/bisect.py', + 'PYMODULE'), + ('importlib._abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/_abc.py', + 'PYMODULE'), + ('importlib.metadata._itertools', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_itertools.py', + 'PYMODULE'), + ('importlib.metadata._functools', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_functools.py', + 'PYMODULE'), + ('importlib.metadata._collections', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_collections.py', + 'PYMODULE'), + ('importlib.metadata._meta', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_meta.py', + 'PYMODULE'), + ('importlib.metadata._adapters', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_adapters.py', + 'PYMODULE'), + ('importlib.metadata._text', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_text.py', + 'PYMODULE'), + ('email.message', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/message.py', + 'PYMODULE'), + ('email.policy', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/policy.py', + 'PYMODULE'), + ('email.contentmanager', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/contentmanager.py', + 'PYMODULE'), + ('email.quoprimime', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/quoprimime.py', + 'PYMODULE'), + ('string', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/string.py', + 'PYMODULE'), + ('email.headerregistry', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/headerregistry.py', + 'PYMODULE'), + ('email._header_value_parser', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/_header_value_parser.py', + 'PYMODULE'), + ('urllib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/__init__.py', + 'PYMODULE'), + ('email.iterators', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/iterators.py', + 'PYMODULE'), + ('email.generator', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/generator.py', + 'PYMODULE'), + ('email._encoded_words', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/_encoded_words.py', + 'PYMODULE'), + ('base64', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/base64.py', + 'PYMODULE'), + ('getopt', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/getopt.py', + 'PYMODULE'), + ('email.charset', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/charset.py', + 'PYMODULE'), + ('email.encoders', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/encoders.py', + 'PYMODULE'), + ('email.base64mime', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/base64mime.py', + 'PYMODULE'), + ('email._policybase', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/_policybase.py', + 'PYMODULE'), + ('email.header', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/header.py', + 'PYMODULE'), + ('email.errors', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/errors.py', + 'PYMODULE'), + ('email.utils', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/utils.py', + 'PYMODULE'), + ('email._parseaddr', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/_parseaddr.py', + 'PYMODULE'), + ('calendar', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/calendar.py', + 'PYMODULE'), + ('urllib.parse', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/parse.py', + 'PYMODULE'), + ('ipaddress', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/ipaddress.py', + 'PYMODULE'), + ('socket', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/socket.py', + 'PYMODULE'), + ('selectors', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/selectors.py', + 'PYMODULE'), + ('quopri', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/quopri.py', + 'PYMODULE'), + ('email', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/__init__.py', + 'PYMODULE'), + ('email.parser', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/parser.py', + 'PYMODULE'), + ('email.feedparser', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/feedparser.py', + 'PYMODULE'), + ('csv', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/csv.py', + 'PYMODULE'), + ('importlib.readers', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/readers.py', + 'PYMODULE'), + ('importlib.resources.readers', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/readers.py', + 'PYMODULE'), + ('importlib.resources._itertools', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/_itertools.py', + 'PYMODULE'), + ('tokenize', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tokenize.py', + 'PYMODULE'), + ('token', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/token.py', + 'PYMODULE'), + ('lzma', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lzma.py', + 'PYMODULE'), + ('_compression', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_compression.py', + 'PYMODULE'), + ('bz2', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/bz2.py', + 'PYMODULE'), + ('contextlib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/contextlib.py', + 'PYMODULE'), + ('_strptime', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_strptime.py', + 'PYMODULE'), + ('threading', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/threading.py', + 'PYMODULE'), + ('_threading_local', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_threading_local.py', + 'PYMODULE'), + ('struct', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/struct.py', + 'PYMODULE'), + ('importlib.util', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/util.py', + 'PYMODULE'), + ('inspect', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/inspect.py', + 'PYMODULE'), + ('dis', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/dis.py', + 'PYMODULE'), + ('opcode', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/opcode.py', + 'PYMODULE'), + ('ast', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/ast.py', + 'PYMODULE'), + ('tracemalloc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tracemalloc.py', + 'PYMODULE'), + ('pickle', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/pickle.py', + 'PYMODULE'), + ('pprint', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/pprint.py', + 'PYMODULE'), + ('dataclasses', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/dataclasses.py', + 'PYMODULE'), + ('_compat_pickle', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_compat_pickle.py', + 'PYMODULE'), + ('fnmatch', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/fnmatch.py', + 'PYMODULE'), + ('_py_abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_py_abc.py', + 'PYMODULE'), + ('stringprep', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/stringprep.py', + 'PYMODULE'), + ('copy', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/copy.py', + 'PYMODULE'), + ('uuid', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/uuid.py', + 'PYMODULE'), + ('subprocess', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/subprocess.py', + 'PYMODULE'), + ('signal', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/signal.py', + 'PYMODULE'), + ('platform', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/platform.py', + 'PYMODULE'), + ('pathlib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/pathlib.py', + 'PYMODULE'), + ('xml.etree.ElementTree', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/ElementTree.py', + 'PYMODULE'), + ('xml.etree.cElementTree', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/cElementTree.py', + 'PYMODULE'), + ('xml.etree.ElementInclude', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/ElementInclude.py', + 'PYMODULE'), + ('xml.parsers.expat', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/parsers/expat.py', + 'PYMODULE'), + ('xml.parsers', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/parsers/__init__.py', + 'PYMODULE'), + ('xml', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/__init__.py', + 'PYMODULE'), + ('xml.sax.expatreader', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/expatreader.py', + 'PYMODULE'), + ('xml.sax.saxutils', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/saxutils.py', + 'PYMODULE'), + ('urllib.request', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/request.py', + 'PYMODULE'), + ('getpass', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/getpass.py', + 'PYMODULE'), + ('nturl2path', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/nturl2path.py', + 'PYMODULE'), + ('ftplib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/ftplib.py', + 'PYMODULE'), + ('netrc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/netrc.py', + 'PYMODULE'), + ('shlex', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/shlex.py', + 'PYMODULE'), + ('mimetypes', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/mimetypes.py', + 'PYMODULE'), + ('http.cookiejar', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/http/cookiejar.py', + 'PYMODULE'), + ('http', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/http/__init__.py', + 'PYMODULE'), + ('ssl', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/ssl.py', + 'PYMODULE'), + ('urllib.response', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/response.py', + 'PYMODULE'), + ('urllib.error', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/error.py', + 'PYMODULE'), + ('http.client', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/http/client.py', + 'PYMODULE'), + ('xml.sax', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/__init__.py', + 'PYMODULE'), + ('xml.sax.handler', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/handler.py', + 'PYMODULE'), + ('xml.sax._exceptions', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/_exceptions.py', + 'PYMODULE'), + ('xml.sax.xmlreader', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/xmlreader.py', + 'PYMODULE'), + ('xml.etree.ElementPath', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/ElementPath.py', + 'PYMODULE'), + ('xml.etree', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/__init__.py', + 'PYMODULE'), + ('datetime', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/datetime.py', + 'PYMODULE'), + ('tkinter.scrolledtext', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/scrolledtext.py', + 'PYMODULE'), + ('tkinter.constants', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/constants.py', + 'PYMODULE'), + ('tkinter.messagebox', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/messagebox.py', + 'PYMODULE'), + ('tkinter.commondialog', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/commondialog.py', + 'PYMODULE'), + ('tkinter.filedialog', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/filedialog.py', + 'PYMODULE'), + ('tkinter.simpledialog', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/simpledialog.py', + 'PYMODULE'), + ('tkinter.dialog', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/dialog.py', + 'PYMODULE'), + ('tkinter.ttk', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/ttk.py', + 'PYMODULE'), + ('tkinter', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/__init__.py', + 'PYMODULE'), + ('logging', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/logging/__init__.py', + 'PYMODULE'), + ('shutil', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/shutil.py', + 'PYMODULE'), + ('tarfile', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tarfile.py', + 'PYMODULE'), + ('gzip', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/gzip.py', + 'PYMODULE')], + [('libpython3.11.so.1.0', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/libpython3.11.so.1.0', + 'BINARY'), + ('python3.11/lib-dynload/_typing.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_typing.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_statistics.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_statistics.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_contextvars.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_contextvars.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_decimal.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_decimal.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_hashlib.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_hashlib.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha3.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha3.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_blake2.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_blake2.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha256.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha256.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_md5.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_md5.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha1.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha1.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha512.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha512.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_random.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_random.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_bisect.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_bisect.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/math.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/math.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/unicodedata.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/unicodedata.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/array.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/array.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/select.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/select.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_socket.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_socket.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_csv.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_csv.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/resource.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/resource.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_lzma.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_lzma.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_bz2.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_bz2.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/zlib.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/zlib.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_struct.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_struct.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/binascii.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/binascii.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_opcode.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_opcode.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_pickle.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_pickle.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_multibytecodec.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_multibytecodec.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_jp.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_jp.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_kr.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_kr.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_iso2022.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_iso2022.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_cn.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_cn.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_tw.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_tw.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_hk.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_hk.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_heapq.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_heapq.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_uuid.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_uuid.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/grp.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/grp.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_posixsubprocess.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_posixsubprocess.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/fcntl.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/fcntl.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_elementtree.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_elementtree.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/pyexpat.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/pyexpat.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/termios.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/termios.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_ssl.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_ssl.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_datetime.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_datetime.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_tkinter.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_tkinter.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('libcrypto.so.3', '/lib/x86_64-linux-gnu/libcrypto.so.3', 'BINARY'), + ('liblzma.so.5', '/lib/x86_64-linux-gnu/liblzma.so.5', 'BINARY'), + ('libbz2.so.1.0', '/lib/x86_64-linux-gnu/libbz2.so.1.0', 'BINARY'), + ('libz.so.1', '/lib/x86_64-linux-gnu/libz.so.1', 'BINARY'), + ('libuuid.so.1', '/lib/x86_64-linux-gnu/libuuid.so.1', 'BINARY'), + ('libssl.so.3', '/lib/x86_64-linux-gnu/libssl.so.3', 'BINARY'), + ('libfreetype.so.6', '/lib/x86_64-linux-gnu/libfreetype.so.6', 'BINARY'), + ('libXau.so.6', '/lib/x86_64-linux-gnu/libXau.so.6', 'BINARY'), + ('libbrotlicommon.so.1', + '/lib/x86_64-linux-gnu/libbrotlicommon.so.1', + 'BINARY'), + ('libtk8.6.so', '/lib/x86_64-linux-gnu/libtk8.6.so', 'BINARY'), + ('libtcl8.6.so', '/lib/x86_64-linux-gnu/libtcl8.6.so', 'BINARY'), + ('libmd.so.0', '/lib/x86_64-linux-gnu/libmd.so.0', 'BINARY'), + ('libexpat.so.1', '/lib/x86_64-linux-gnu/libexpat.so.1', 'BINARY'), + ('libXext.so.6', '/lib/x86_64-linux-gnu/libXext.so.6', 'BINARY'), + ('libXdmcp.so.6', '/lib/x86_64-linux-gnu/libXdmcp.so.6', 'BINARY'), + ('libbrotlidec.so.1', '/lib/x86_64-linux-gnu/libbrotlidec.so.1', 'BINARY'), + ('libpng16.so.16', '/lib/x86_64-linux-gnu/libpng16.so.16', 'BINARY'), + ('libfontconfig.so.1', '/lib/x86_64-linux-gnu/libfontconfig.so.1', 'BINARY'), + ('libXrender.so.1', '/lib/x86_64-linux-gnu/libXrender.so.1', 'BINARY'), + ('libXft.so.2', '/lib/x86_64-linux-gnu/libXft.so.2', 'BINARY'), + ('libX11.so.6', '/lib/x86_64-linux-gnu/libX11.so.6', 'BINARY'), + ('libbsd.so.0', '/lib/x86_64-linux-gnu/libbsd.so.0', 'BINARY'), + ('libXss.so.1', '/lib/x86_64-linux-gnu/libXss.so.1', 'BINARY')], + [], + [], + [('cluster_cleanup.txt', + '/home/ubuntu/pointcab_renamer/cluster_cleanup.txt', + 'DATA'), + ('_tcl_data/tcl8/tcltest-2.5.5.tm', + '/usr/share/tcltk/tcl8.6/tcl8/tcltest-2.5.5.tm', + 'DATA'), + ('_tk_data/choosedir.tcl', '/usr/share/tcltk/tk8.6/choosedir.tcl', 'DATA'), + ('_tcl_data/msgs/fr_ca.msg', + '/usr/share/tcltk/tcl8.6/msgs/fr_ca.msg', + 'DATA'), + ('_tcl_data/msgs/fi.msg', '/usr/share/tcltk/tcl8.6/msgs/fi.msg', 'DATA'), + ('_tcl_data/encoding/cp874.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp874.enc', + 'DATA'), + ('_tk_data/ttk/ttk.tcl', '/usr/share/tcltk/tk8.6/ttk/ttk.tcl', 'DATA'), + ('_tcl_data/msgs/bn_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/bn_in.msg', + 'DATA'), + ('_tcl_data/encoding/cp850.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp850.enc', + 'DATA'), + ('_tcl_data/msgs/hu.msg', '/usr/share/tcltk/tcl8.6/msgs/hu.msg', 'DATA'), + ('_tk_data/dialog.tcl', '/usr/share/tcltk/tk8.6/dialog.tcl', 'DATA'), + ('_tk_data/ttk/sizegrip.tcl', + '/usr/share/tcltk/tk8.6/ttk/sizegrip.tcl', + 'DATA'), + ('_tcl_data/msgs/mk.msg', '/usr/share/tcltk/tcl8.6/msgs/mk.msg', 'DATA'), + ('_tk_data/images/logoMed.gif', + '/usr/share/tcltk/tk8.6/images/logoMed.gif', + 'DATA'), + ('_tcl_data/msgs/sq.msg', '/usr/share/tcltk/tcl8.6/msgs/sq.msg', 'DATA'), + ('_tk_data/ttk/vistaTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/vistaTheme.tcl', + 'DATA'), + ('_tk_data/msgs/pl.msg', '/usr/share/tcltk/tk8.6/msgs/pl.msg', 'DATA'), + ('_tk_data/ttk/scrollbar.tcl', + '/usr/share/tcltk/tk8.6/ttk/scrollbar.tcl', + 'DATA'), + ('_tk_data/panedwindow.tcl', + '/usr/share/tcltk/tk8.6/panedwindow.tcl', + 'DATA'), + ('_tcl_data/encoding/iso2022.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso2022.enc', + 'DATA'), + ('_tcl_data/encoding/euc-jp.enc', + '/usr/share/tcltk/tcl8.6/encoding/euc-jp.enc', + 'DATA'), + ('_tcl_data/tcl8/msgcat-1.6.1.tm', + '/usr/share/tcltk/tcl8.6/tcl8/msgcat-1.6.1.tm', + 'DATA'), + ('_tcl_data/encoding/dingbats.enc', + '/usr/share/tcltk/tcl8.6/encoding/dingbats.enc', + 'DATA'), + ('_tcl_data/encoding/macRoman.enc', + '/usr/share/tcltk/tcl8.6/encoding/macRoman.enc', + 'DATA'), + ('_tk_data/tkAppInit.c', '/usr/share/tcltk/tk8.6/tkAppInit.c', 'DATA'), + ('_tk_data/ttk/combobox.tcl', + '/usr/share/tcltk/tk8.6/ttk/combobox.tcl', + 'DATA'), + ('_tcl_data/msgs/fr.msg', '/usr/share/tcltk/tcl8.6/msgs/fr.msg', 'DATA'), + ('_tcl_data/tclIndex', '/usr/share/tcltk/tcl8.6/tclIndex', 'DATA'), + ('_tk_data/msgs/en.msg', '/usr/share/tcltk/tk8.6/msgs/en.msg', 'DATA'), + ('_tcl_data/msgs/en_ph.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_ph.msg', + 'DATA'), + ('_tk_data/images/pwrdLogo75.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo75.gif', + 'DATA'), + ('_tcl_data/msgs/zh_cn.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_cn.msg', + 'DATA'), + ('_tcl_data/encoding/cp866.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp866.enc', + 'DATA'), + ('_tcl_data/msgs/mr.msg', '/usr/share/tcltk/tcl8.6/msgs/mr.msg', 'DATA'), + ('_tk_data/ttk/winTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/winTheme.tcl', + 'DATA'), + ('_tcl_data/msgs/es_hn.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_hn.msg', + 'DATA'), + ('_tcl_data/msgs/th.msg', '/usr/share/tcltk/tcl8.6/msgs/th.msg', 'DATA'), + ('_tk_data/msgs/hu.msg', '/usr/share/tcltk/tk8.6/msgs/hu.msg', 'DATA'), + ('_tk_data/images/pwrdLogo175.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo175.gif', + 'DATA'), + ('_tcl_data/encoding/iso8859-9.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-9.enc', + 'DATA'), + ('_tk_data/tkfbox.tcl', '/usr/share/tcltk/tk8.6/tkfbox.tcl', 'DATA'), + ('_tcl_data/msgs/sk.msg', '/usr/share/tcltk/tcl8.6/msgs/sk.msg', 'DATA'), + ('_tcl_data/encoding/macUkraine.enc', + '/usr/share/tcltk/tcl8.6/encoding/macUkraine.enc', + 'DATA'), + ('_tcl_data/encoding/ebcdic.enc', + '/usr/share/tcltk/tcl8.6/encoding/ebcdic.enc', + 'DATA'), + ('_tk_data/ttk/clamTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/clamTheme.tcl', + 'DATA'), + ('_tcl_data/encoding/macJapan.enc', + '/usr/share/tcltk/tcl8.6/encoding/macJapan.enc', + 'DATA'), + ('_tk_data/msgs/fi.msg', '/usr/share/tcltk/tk8.6/msgs/fi.msg', 'DATA'), + ('_tcl_data/tm.tcl', '/usr/share/tcltk/tcl8.6/tm.tcl', 'DATA'), + ('_tk_data/images/logoLarge.gif', + '/usr/share/tcltk/tk8.6/images/logoLarge.gif', + 'DATA'), + ('_tcl_data/msgs/ja.msg', '/usr/share/tcltk/tcl8.6/msgs/ja.msg', 'DATA'), + ('_tcl_data/msgs/gl.msg', '/usr/share/tcltk/tcl8.6/msgs/gl.msg', 'DATA'), + ('_tk_data/safetk.tcl', '/usr/share/tcltk/tk8.6/safetk.tcl', 'DATA'), + ('_tcl_data/msgs/kok.msg', '/usr/share/tcltk/tcl8.6/msgs/kok.msg', 'DATA'), + ('_tcl_data/msgs/mr_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/mr_in.msg', + 'DATA'), + ('_tk_data/iconlist.tcl', '/usr/share/tcltk/tk8.6/iconlist.tcl', 'DATA'), + ('_tcl_data/msgs/es_ni.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ni.msg', + 'DATA'), + ('_tcl_data/encoding/macRomania.enc', + '/usr/share/tcltk/tcl8.6/encoding/macRomania.enc', + 'DATA'), + ('_tk_data/fontchooser.tcl', + '/usr/share/tcltk/tk8.6/fontchooser.tcl', + 'DATA'), + ('_tk_data/ttk/entry.tcl', '/usr/share/tcltk/tk8.6/ttk/entry.tcl', 'DATA'), + ('_tcl_data/encoding/cp737.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp737.enc', + 'DATA'), + ('_tcl_data/msgs/zh_tw.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_tw.msg', + 'DATA'), + ('_tcl_data/msgs/es_co.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_co.msg', + 'DATA'), + ('_tcl_data/msgs/pl.msg', '/usr/share/tcltk/tcl8.6/msgs/pl.msg', 'DATA'), + ('_tk_data/images/logo64.gif', + '/usr/share/tcltk/tk8.6/images/logo64.gif', + 'DATA'), + ('_tcl_data/msgs/sl.msg', '/usr/share/tcltk/tcl8.6/msgs/sl.msg', 'DATA'), + ('_tk_data/msgs/en_gb.msg', '/usr/share/tcltk/tk8.6/msgs/en_gb.msg', 'DATA'), + ('_tcl_data/msgs/kl.msg', '/usr/share/tcltk/tcl8.6/msgs/kl.msg', 'DATA'), + ('_tcl_data/encoding/cp864.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp864.enc', + 'DATA'), + ('_tcl_data/msgs/lv.msg', '/usr/share/tcltk/tcl8.6/msgs/lv.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-8.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-8.enc', + 'DATA'), + ('_tcl_data/msgs/de_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/de_be.msg', + 'DATA'), + ('_tcl_data/encoding/cp936.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp936.enc', + 'DATA'), + ('_tcl_data/msgs/hr.msg', '/usr/share/tcltk/tcl8.6/msgs/hr.msg', 'DATA'), + ('_tcl_data/encoding/big5.enc', + '/usr/share/tcltk/tcl8.6/encoding/big5.enc', + 'DATA'), + ('_tcl_data/encoding/macIceland.enc', + '/usr/share/tcltk/tcl8.6/encoding/macIceland.enc', + 'DATA'), + ('_tcl_data/history.tcl', '/usr/share/tcltk/tcl8.6/history.tcl', 'DATA'), + ('_tcl_data/msgs/hi.msg', '/usr/share/tcltk/tcl8.6/msgs/hi.msg', 'DATA'), + ('_tcl_data/msgs/kw.msg', '/usr/share/tcltk/tcl8.6/msgs/kw.msg', 'DATA'), + ('_tcl_data/msgs/en_au.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_au.msg', + 'DATA'), + ('_tk_data/ttk/scale.tcl', '/usr/share/tcltk/tk8.6/ttk/scale.tcl', 'DATA'), + ('_tcl_data/msgs/fa_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/fa_in.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-4.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-4.enc', + 'DATA'), + ('_tcl_data/msgs/ru_ua.msg', + '/usr/share/tcltk/tcl8.6/msgs/ru_ua.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-6.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-6.enc', + 'DATA'), + ('_tcl_data/encoding/cp1251.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1251.enc', + 'DATA'), + ('_tcl_data/msgs/es_pe.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_pe.msg', + 'DATA'), + ('_tcl_data/opt0.4/pkgIndex.tcl', + '/usr/share/tcltk/tcl8.6/opt0.4/pkgIndex.tcl', + 'DATA'), + ('_tcl_data/msgs/et.msg', '/usr/share/tcltk/tcl8.6/msgs/et.msg', 'DATA'), + ('_tcl_data/msgs/kl_gl.msg', + '/usr/share/tcltk/tcl8.6/msgs/kl_gl.msg', + 'DATA'), + ('_tcl_data/encoding/cp1256.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1256.enc', + 'DATA'), + ('_tcl_data/encoding/jis0201.enc', + '/usr/share/tcltk/tcl8.6/encoding/jis0201.enc', + 'DATA'), + ('_tk_data/text.tcl', '/usr/share/tcltk/tk8.6/text.tcl', 'DATA'), + ('_tk_data/images/tai-ku.gif', + '/usr/share/tcltk/tk8.6/images/tai-ku.gif', + 'DATA'), + ('_tcl_data/msgs/kok_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/kok_in.msg', + 'DATA'), + ('_tk_data/ttk/xpTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/xpTheme.tcl', + 'DATA'), + ('_tk_data/icons.tcl', '/usr/share/tcltk/tk8.6/icons.tcl', 'DATA'), + ('_tcl_data/msgs/sv.msg', '/usr/share/tcltk/tcl8.6/msgs/sv.msg', 'DATA'), + ('_tk_data/ttk/aquaTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/aquaTheme.tcl', + 'DATA'), + ('_tcl_data/msgs/lt.msg', '/usr/share/tcltk/tcl8.6/msgs/lt.msg', 'DATA'), + ('_tcl_data/msgs/fo_fo.msg', + '/usr/share/tcltk/tcl8.6/msgs/fo_fo.msg', + 'DATA'), + ('_tcl_data/encoding/macCentEuro.enc', + '/usr/share/tcltk/tcl8.6/encoding/macCentEuro.enc', + 'DATA'), + ('_tcl_data/msgs/en_bw.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_bw.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-14.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-14.enc', + 'DATA'), + ('_tk_data/ttk/altTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/altTheme.tcl', + 'DATA'), + ('_tcl_data/encoding/gb2312.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb2312.enc', + 'DATA'), + ('_tcl_data/encoding/cp1252.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1252.enc', + 'DATA'), + ('_tcl_data/encoding/cp1255.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1255.enc', + 'DATA'), + ('_tk_data/palette.tcl', '/usr/share/tcltk/tk8.6/palette.tcl', 'DATA'), + ('_tcl_data/msgs/eo.msg', '/usr/share/tcltk/tcl8.6/msgs/eo.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-13.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-13.enc', + 'DATA'), + ('_tcl_data/msgs/nl.msg', '/usr/share/tcltk/tcl8.6/msgs/nl.msg', 'DATA'), + ('_tcl_data/msgs/en_ie.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_ie.msg', + 'DATA'), + ('_tcl_data/msgs/es_bo.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_bo.msg', + 'DATA'), + ('_tcl_data/msgs/ta.msg', '/usr/share/tcltk/tcl8.6/msgs/ta.msg', 'DATA'), + ('_tcl_data/encoding/euc-kr.enc', + '/usr/share/tcltk/tcl8.6/encoding/euc-kr.enc', + 'DATA'), + ('_tk_data/scale.tcl', '/usr/share/tcltk/tk8.6/scale.tcl', 'DATA'), + ('_tcl_data/tclAppInit.c', '/usr/share/tcltk/tcl8.6/tclAppInit.c', 'DATA'), + ('_tcl_data/encoding/iso2022-kr.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso2022-kr.enc', + 'DATA'), + ('_tcl_data/msgs/ar_jo.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_jo.msg', + 'DATA'), + ('_tcl_data/msgs/en_gb.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_gb.msg', + 'DATA'), + ('_tcl_data/msgs/sh.msg', '/usr/share/tcltk/tcl8.6/msgs/sh.msg', 'DATA'), + ('_tcl_data/msgs/da.msg', '/usr/share/tcltk/tcl8.6/msgs/da.msg', 'DATA'), + ('_tcl_data/msgs/nl_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/nl_be.msg', + 'DATA'), + ('_tcl_data/encoding/cp852.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp852.enc', + 'DATA'), + ('_tcl_data/msgs/be.msg', '/usr/share/tcltk/tcl8.6/msgs/be.msg', 'DATA'), + ('_tk_data/msgs/nl.msg', '/usr/share/tcltk/tk8.6/msgs/nl.msg', 'DATA'), + ('_tcl_data/msgs/fa_ir.msg', + '/usr/share/tcltk/tcl8.6/msgs/fa_ir.msg', + 'DATA'), + ('_tcl_data/msgs/it.msg', '/usr/share/tcltk/tcl8.6/msgs/it.msg', 'DATA'), + ('_tk_data/ttk/cursors.tcl', + '/usr/share/tcltk/tk8.6/ttk/cursors.tcl', + 'DATA'), + ('_tcl_data/msgs/zh_sg.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_sg.msg', + 'DATA'), + ('_tk_data/spinbox.tcl', '/usr/share/tcltk/tk8.6/spinbox.tcl', 'DATA'), + ('_tk_data/msgs/es.msg', '/usr/share/tcltk/tk8.6/msgs/es.msg', 'DATA'), + ('_tk_data/msgs/el.msg', '/usr/share/tcltk/tk8.6/msgs/el.msg', 'DATA'), + ('_tcl_data/msgs/ar_sy.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_sy.msg', + 'DATA'), + ('_tcl_data/msgs/ar.msg', '/usr/share/tcltk/tcl8.6/msgs/ar.msg', 'DATA'), + ('_tcl_data/msgs/ga_ie.msg', + '/usr/share/tcltk/tcl8.6/msgs/ga_ie.msg', + 'DATA'), + ('_tk_data/msgs/sv.msg', '/usr/share/tcltk/tk8.6/msgs/sv.msg', 'DATA'), + ('_tk_data/tclIndex', '/usr/share/tcltk/tk8.6/tclIndex', 'DATA'), + ('_tcl_data/msgs/uk.msg', '/usr/share/tcltk/tcl8.6/msgs/uk.msg', 'DATA'), + ('_tcl_data/word.tcl', '/usr/share/tcltk/tcl8.6/word.tcl', 'DATA'), + ('_tcl_data/msgs/ga.msg', '/usr/share/tcltk/tcl8.6/msgs/ga.msg', 'DATA'), + ('_tcl_data/msgs/en_za.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_za.msg', + 'DATA'), + ('_tcl_data/msgs/fr_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/fr_be.msg', + 'DATA'), + ('_tk_data/msgbox.tcl', '/usr/share/tcltk/tk8.6/msgbox.tcl', 'DATA'), + ('_tcl_data/encoding/cp1257.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1257.enc', + 'DATA'), + ('_tcl_data/encoding/iso8859-3.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-3.enc', + 'DATA'), + ('_tcl_data/encoding/euc-cn.enc', + '/usr/share/tcltk/tcl8.6/encoding/euc-cn.enc', + 'DATA'), + ('_tcl_data/msgs/id.msg', '/usr/share/tcltk/tcl8.6/msgs/id.msg', 'DATA'), + ('_tk_data/ttk/menubutton.tcl', + '/usr/share/tcltk/tk8.6/ttk/menubutton.tcl', + 'DATA'), + ('_tk_data/tearoff.tcl', '/usr/share/tcltk/tk8.6/tearoff.tcl', 'DATA'), + ('_tk_data/optMenu.tcl', '/usr/share/tcltk/tk8.6/optMenu.tcl', 'DATA'), + ('_tcl_data/tcl8/platform/shell-1.1.4.tm', + '/usr/share/tcltk/tcl8.6/tcl8/platform/shell-1.1.4.tm', + 'DATA'), + ('_tcl_data/encoding/cp860.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp860.enc', + 'DATA'), + ('_tcl_data/encoding/macCroatian.enc', + '/usr/share/tcltk/tcl8.6/encoding/macCroatian.enc', + 'DATA'), + ('_tcl_data/encoding/cp1253.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1253.enc', + 'DATA'), + ('_tcl_data/msgs/gv.msg', '/usr/share/tcltk/tcl8.6/msgs/gv.msg', 'DATA'), + ('_tcl_data/init.tcl', '/usr/share/tcltk/tcl8.6/init.tcl', 'DATA'), + ('_tcl_data/msgs/fr_ch.msg', + '/usr/share/tcltk/tcl8.6/msgs/fr_ch.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-10.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-10.enc', + 'DATA'), + ('_tcl_data/msgs/bg.msg', '/usr/share/tcltk/tcl8.6/msgs/bg.msg', 'DATA'), + ('_tcl_data/msgs/ro.msg', '/usr/share/tcltk/tcl8.6/msgs/ro.msg', 'DATA'), + ('_tcl_data/encoding/cp857.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp857.enc', + 'DATA'), + ('_tcl_data/msgs/gl_es.msg', + '/usr/share/tcltk/tcl8.6/msgs/gl_es.msg', + 'DATA'), + ('_tcl_data/msgs/es_uy.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_uy.msg', + 'DATA'), + ('_tk_data/ttk/panedwindow.tcl', + '/usr/share/tcltk/tk8.6/ttk/panedwindow.tcl', + 'DATA'), + ('_tcl_data/msgs/af.msg', '/usr/share/tcltk/tcl8.6/msgs/af.msg', 'DATA'), + ('_tcl_data/msgs/de_at.msg', + '/usr/share/tcltk/tcl8.6/msgs/de_at.msg', + 'DATA'), + ('_tcl_data/auto.tcl', '/usr/share/tcltk/tcl8.6/auto.tcl', 'DATA'), + ('_tcl_data/msgs/ms_my.msg', + '/usr/share/tcltk/tcl8.6/msgs/ms_my.msg', + 'DATA'), + ('_tcl_data/msgs/ms.msg', '/usr/share/tcltk/tcl8.6/msgs/ms.msg', 'DATA'), + ('_tcl_data/encoding/gb12345.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb12345.enc', + 'DATA'), + ('_tcl_data/msgs/ru.msg', '/usr/share/tcltk/tcl8.6/msgs/ru.msg', 'DATA'), + ('_tcl_data/msgs/sr.msg', '/usr/share/tcltk/tcl8.6/msgs/sr.msg', 'DATA'), + ('_tcl_data/msgs/zh.msg', '/usr/share/tcltk/tcl8.6/msgs/zh.msg', 'DATA'), + ('_tcl_data/msgs/mt.msg', '/usr/share/tcltk/tcl8.6/msgs/mt.msg', 'DATA'), + ('_tcl_data/encoding/jis0208.enc', + '/usr/share/tcltk/tcl8.6/encoding/jis0208.enc', + 'DATA'), + ('_tcl_data/encoding/iso8859-15.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-15.enc', + 'DATA'), + ('_tk_data/msgs/zh_cn.msg', '/usr/share/tcltk/tk8.6/msgs/zh_cn.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-16.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-16.enc', + 'DATA'), + ('_tcl_data/encoding/koi8-u.enc', + '/usr/share/tcltk/tcl8.6/encoding/koi8-u.enc', + 'DATA'), + ('_tk_data/unsupported.tcl', + '/usr/share/tcltk/tk8.6/unsupported.tcl', + 'DATA'), + ('_tcl_data/encoding/cp865.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp865.enc', + 'DATA'), + ('_tcl_data/msgs/es_ar.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ar.msg', + 'DATA'), + ('_tk_data/msgs/cs.msg', '/usr/share/tcltk/tk8.6/msgs/cs.msg', 'DATA'), + ('_tcl_data/encoding/koi8-r.enc', + '/usr/share/tcltk/tcl8.6/encoding/koi8-r.enc', + 'DATA'), + ('_tcl_data/parray.tcl', '/usr/share/tcltk/tcl8.6/parray.tcl', 'DATA'), + ('_tk_data/ttk/classicTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/classicTheme.tcl', + 'DATA'), + ('_tcl_data/encoding/cp1254.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1254.enc', + 'DATA'), + ('_tcl_data/encoding/symbol.enc', + '/usr/share/tcltk/tcl8.6/encoding/symbol.enc', + 'DATA'), + ('_tcl_data/msgs/sw.msg', '/usr/share/tcltk/tcl8.6/msgs/sw.msg', 'DATA'), + ('_tk_data/images/pwrdLogo.eps', + '/usr/share/tcltk/tk8.6/images/pwrdLogo.eps', + 'DATA'), + ('_tcl_data/msgs/es.msg', '/usr/share/tcltk/tcl8.6/msgs/es.msg', 'DATA'), + ('_tcl_data/package.tcl', '/usr/share/tcltk/tcl8.6/package.tcl', 'DATA'), + ('_tcl_data/encoding/macTurkish.enc', + '/usr/share/tcltk/tcl8.6/encoding/macTurkish.enc', + 'DATA'), + ('_tk_data/bgerror.tcl', '/usr/share/tcltk/tk8.6/bgerror.tcl', 'DATA'), + ('_tk_data/listbox.tcl', '/usr/share/tcltk/tk8.6/listbox.tcl', 'DATA'), + ('_tcl_data/msgs/es_mx.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_mx.msg', + 'DATA'), + ('_tk_data/msgs/it.msg', '/usr/share/tcltk/tk8.6/msgs/it.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-2.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-2.enc', + 'DATA'), + ('_tcl_data/encoding/cp1250.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1250.enc', + 'DATA'), + ('_tcl_data/encoding/macThai.enc', + '/usr/share/tcltk/tcl8.6/encoding/macThai.enc', + 'DATA'), + ('_tcl_data/encoding/macGreek.enc', + '/usr/share/tcltk/tcl8.6/encoding/macGreek.enc', + 'DATA'), + ('_tcl_data/opt0.4/optparse.tcl', + '/usr/share/tcltk/tcl8.6/opt0.4/optparse.tcl', + 'DATA'), + ('_tk_data/msgs/ru.msg', '/usr/share/tcltk/tk8.6/msgs/ru.msg', 'DATA'), + ('_tcl_data/encoding/ksc5601.enc', + '/usr/share/tcltk/tcl8.6/encoding/ksc5601.enc', + 'DATA'), + ('_tcl_data/encoding/jis0212.enc', + '/usr/share/tcltk/tcl8.6/encoding/jis0212.enc', + 'DATA'), + ('_tcl_data/encoding/tis-620.enc', + '/usr/share/tcltk/tcl8.6/encoding/tis-620.enc', + 'DATA'), + ('_tk_data/tk.tcl', '/usr/share/tcltk/tk8.6/tk.tcl', 'DATA'), + ('_tk_data/console.tcl', '/usr/share/tcltk/tk8.6/console.tcl', 'DATA'), + ('_tcl_data/msgs/it_ch.msg', + '/usr/share/tcltk/tcl8.6/msgs/it_ch.msg', + 'DATA'), + ('_tcl_data/encoding/macDingbats.enc', + '/usr/share/tcltk/tcl8.6/encoding/macDingbats.enc', + 'DATA'), + ('_tcl_data/encoding/cp437.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp437.enc', + 'DATA'), + ('_tcl_data/msgs/en_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_be.msg', + 'DATA'), + ('_tcl_data/tcl8/platform-1.0.19.tm', + '/usr/share/tcltk/tcl8.6/tcl8/platform-1.0.19.tm', + 'DATA'), + ('_tcl_data/msgs/pt_br.msg', + '/usr/share/tcltk/tcl8.6/msgs/pt_br.msg', + 'DATA'), + ('_tcl_data/clock.tcl', '/usr/share/tcltk/tcl8.6/clock.tcl', 'DATA'), + ('_tk_data/ttk/spinbox.tcl', + '/usr/share/tcltk/tk8.6/ttk/spinbox.tcl', + 'DATA'), + ('_tcl_data/encoding/iso8859-7.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-7.enc', + 'DATA'), + ('_tk_data/obsolete.tcl', '/usr/share/tcltk/tk8.6/obsolete.tcl', 'DATA'), + ('_tcl_data/tcl8/http-2.9.8.tm', + '/usr/share/tcltk/tcl8.6/tcl8/http-2.9.8.tm', + 'DATA'), + ('_tcl_data/msgs/es_ec.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ec.msg', + 'DATA'), + ('_tk_data/msgs/pt.msg', '/usr/share/tcltk/tk8.6/msgs/pt.msg', 'DATA'), + ('_tcl_data/encoding/cns11643.enc', + '/usr/share/tcltk/tcl8.6/encoding/cns11643.enc', + 'DATA'), + ('_tk_data/focus.tcl', '/usr/share/tcltk/tk8.6/focus.tcl', 'DATA'), + ('_tcl_data/msgs/is.msg', '/usr/share/tcltk/tcl8.6/msgs/is.msg', 'DATA'), + ('_tcl_data/msgs/en_hk.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_hk.msg', + 'DATA'), + ('_tcl_data/msgs/bn.msg', '/usr/share/tcltk/tcl8.6/msgs/bn.msg', 'DATA'), + ('_tcl_data/encoding/cp861.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp861.enc', + 'DATA'), + ('_tcl_data/msgs/kw_gb.msg', + '/usr/share/tcltk/tcl8.6/msgs/kw_gb.msg', + 'DATA'), + ('_tcl_data/encoding/cp869.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp869.enc', + 'DATA'), + ('_tcl_data/msgs/en_zw.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_zw.msg', + 'DATA'), + ('_tk_data/button.tcl', '/usr/share/tcltk/tk8.6/button.tcl', 'DATA'), + ('_tk_data/ttk/treeview.tcl', + '/usr/share/tcltk/tk8.6/ttk/treeview.tcl', + 'DATA'), + ('_tcl_data/msgs/es_cl.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_cl.msg', + 'DATA'), + ('_tk_data/ttk/defaults.tcl', + '/usr/share/tcltk/tk8.6/ttk/defaults.tcl', + 'DATA'), + ('_tcl_data/encoding/cp932.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp932.enc', + 'DATA'), + ('_tk_data/xmfbox.tcl', '/usr/share/tcltk/tk8.6/xmfbox.tcl', 'DATA'), + ('_tcl_data/msgs/el.msg', '/usr/share/tcltk/tcl8.6/msgs/el.msg', 'DATA'), + ('_tcl_data/msgs/fo.msg', '/usr/share/tcltk/tcl8.6/msgs/fo.msg', 'DATA'), + ('_tcl_data/encoding/cp949.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp949.enc', + 'DATA'), + ('_tcl_data/safe.tcl', '/usr/share/tcltk/tcl8.6/safe.tcl', 'DATA'), + ('_tk_data/images/pwrdLogo150.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo150.gif', + 'DATA'), + ('_tk_data/entry.tcl', '/usr/share/tcltk/tk8.6/entry.tcl', 'DATA'), + ('_tcl_data/msgs/vi.msg', '/usr/share/tcltk/tcl8.6/msgs/vi.msg', 'DATA'), + ('_tcl_data/http1.0/pkgIndex.tcl', + '/usr/share/tcltk/tcl8.6/http1.0/pkgIndex.tcl', + 'DATA'), + ('_tk_data/msgs/de.msg', '/usr/share/tcltk/tk8.6/msgs/de.msg', 'DATA'), + ('_tcl_data/encoding/cp855.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp855.enc', + 'DATA'), + ('_tcl_data/msgs/ar_lb.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_lb.msg', + 'DATA'), + ('_tcl_data/msgs/eu_es.msg', + '/usr/share/tcltk/tcl8.6/msgs/eu_es.msg', + 'DATA'), + ('_tcl_data/msgs/cs.msg', '/usr/share/tcltk/tcl8.6/msgs/cs.msg', 'DATA'), + ('_tcl_data/encoding/cp862.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp862.enc', + 'DATA'), + ('_tcl_data/msgs/pt.msg', '/usr/share/tcltk/tcl8.6/msgs/pt.msg', 'DATA'), + ('_tk_data/ttk/progress.tcl', + '/usr/share/tcltk/tk8.6/ttk/progress.tcl', + 'DATA'), + ('_tcl_data/encoding/gb2312-raw.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb2312-raw.enc', + 'DATA'), + ('_tk_data/clrpick.tcl', '/usr/share/tcltk/tk8.6/clrpick.tcl', 'DATA'), + ('_tcl_data/encoding/shiftjis.enc', + '/usr/share/tcltk/tcl8.6/encoding/shiftjis.enc', + 'DATA'), + ('_tcl_data/msgs/nb.msg', '/usr/share/tcltk/tcl8.6/msgs/nb.msg', 'DATA'), + ('_tcl_data/msgs/he.msg', '/usr/share/tcltk/tcl8.6/msgs/he.msg', 'DATA'), + ('_tk_data/msgs/fr.msg', '/usr/share/tcltk/tk8.6/msgs/fr.msg', 'DATA'), + ('_tk_data/comdlg.tcl', '/usr/share/tcltk/tk8.6/comdlg.tcl', 'DATA'), + ('_tcl_data/http1.0/http.tcl', + '/usr/share/tcltk/tcl8.6/http1.0/http.tcl', + 'DATA'), + ('_tcl_data/msgs/fa.msg', '/usr/share/tcltk/tcl8.6/msgs/fa.msg', 'DATA'), + ('_tcl_data/encoding/gb1988.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb1988.enc', + 'DATA'), + ('_tk_data/images/logo100.gif', + '/usr/share/tcltk/tk8.6/images/logo100.gif', + 'DATA'), + ('_tcl_data/msgs/es_gt.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_gt.msg', + 'DATA'), + ('_tcl_data/msgs/ta_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/ta_in.msg', + 'DATA'), + ('_tcl_data/encoding/cp863.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp863.enc', + 'DATA'), + ('_tcl_data/msgs/tr.msg', '/usr/share/tcltk/tcl8.6/msgs/tr.msg', 'DATA'), + ('_tcl_data/msgs/es_pr.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_pr.msg', + 'DATA'), + ('_tcl_data/msgs/ca.msg', '/usr/share/tcltk/tcl8.6/msgs/ca.msg', 'DATA'), + ('_tk_data/menu.tcl', '/usr/share/tcltk/tk8.6/menu.tcl', 'DATA'), + ('_tk_data/msgs/da.msg', '/usr/share/tcltk/tk8.6/msgs/da.msg', 'DATA'), + ('_tk_data/images/README', '/usr/share/tcltk/tk8.6/images/README', 'DATA'), + ('_tcl_data/encoding/iso8859-1.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-1.enc', + 'DATA'), + ('_tcl_data/msgs/es_py.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_py.msg', + 'DATA'), + ('_tk_data/ttk/notebook.tcl', + '/usr/share/tcltk/tk8.6/ttk/notebook.tcl', + 'DATA'), + ('_tcl_data/msgs/ar_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_in.msg', + 'DATA'), + ('_tcl_data/encoding/cp1258.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1258.enc', + 'DATA'), + ('_tcl_data/msgs/hi_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/hi_in.msg', + 'DATA'), + ('_tcl_data/msgs/de.msg', '/usr/share/tcltk/tcl8.6/msgs/de.msg', 'DATA'), + ('_tk_data/ttk/button.tcl', '/usr/share/tcltk/tk8.6/ttk/button.tcl', 'DATA'), + ('_tk_data/msgs/eo.msg', '/usr/share/tcltk/tk8.6/msgs/eo.msg', 'DATA'), + ('_tcl_data/msgs/id_id.msg', + '/usr/share/tcltk/tcl8.6/msgs/id_id.msg', + 'DATA'), + ('_tcl_data/msgs/en_sg.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_sg.msg', + 'DATA'), + ('_tcl_data/msgs/es_ve.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ve.msg', + 'DATA'), + ('_tcl_data/msgs/te_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/te_in.msg', + 'DATA'), + ('_tcl_data/encoding/macCyrillic.enc', + '/usr/share/tcltk/tcl8.6/encoding/macCyrillic.enc', + 'DATA'), + ('_tcl_data/encoding/cp950.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp950.enc', + 'DATA'), + ('_tcl_data/encoding/iso8859-11.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-11.enc', + 'DATA'), + ('_tcl_data/msgs/en_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_in.msg', + 'DATA'), + ('_tcl_data/encoding/iso2022-jp.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso2022-jp.enc', + 'DATA'), + ('_tk_data/ttk/utils.tcl', '/usr/share/tcltk/tk8.6/ttk/utils.tcl', 'DATA'), + ('_tcl_data/msgs/te.msg', '/usr/share/tcltk/tcl8.6/msgs/te.msg', 'DATA'), + ('_tcl_data/msgs/ko_kr.msg', + '/usr/share/tcltk/tcl8.6/msgs/ko_kr.msg', + 'DATA'), + ('_tk_data/images/logo.eps', + '/usr/share/tcltk/tk8.6/images/logo.eps', + 'DATA'), + ('_tcl_data/msgs/gv_gb.msg', + '/usr/share/tcltk/tcl8.6/msgs/gv_gb.msg', + 'DATA'), + ('_tk_data/scrlbar.tcl', '/usr/share/tcltk/tk8.6/scrlbar.tcl', 'DATA'), + ('_tcl_data/msgs/en_ca.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_ca.msg', + 'DATA'), + ('_tk_data/images/pwrdLogo200.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo200.gif', + 'DATA'), + ('_tcl_data/msgs/es_do.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_do.msg', + 'DATA'), + ('_tcl_data/msgs/nn.msg', '/usr/share/tcltk/tcl8.6/msgs/nn.msg', 'DATA'), + ('_tk_data/megawidget.tcl', '/usr/share/tcltk/tk8.6/megawidget.tcl', 'DATA'), + ('_tcl_data/encoding/ascii.enc', + '/usr/share/tcltk/tcl8.6/encoding/ascii.enc', + 'DATA'), + ('_tcl_data/encoding/cp775.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp775.enc', + 'DATA'), + ('_tcl_data/msgs/en_nz.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_nz.msg', + 'DATA'), + ('_tcl_data/msgs/eu.msg', '/usr/share/tcltk/tcl8.6/msgs/eu.msg', 'DATA'), + ('_tcl_data/msgs/es_cr.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_cr.msg', + 'DATA'), + ('_tcl_data/msgs/es_sv.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_sv.msg', + 'DATA'), + ('_tcl_data/msgs/af_za.msg', + '/usr/share/tcltk/tcl8.6/msgs/af_za.msg', + 'DATA'), + ('_tcl_data/msgs/es_pa.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_pa.msg', + 'DATA'), + ('_tk_data/ttk/fonts.tcl', '/usr/share/tcltk/tk8.6/ttk/fonts.tcl', 'DATA'), + ('_tk_data/images/pwrdLogo100.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo100.gif', + 'DATA'), + ('_tcl_data/msgs/zh_hk.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_hk.msg', + 'DATA'), + ('_tk_data/mkpsenc.tcl', '/usr/share/tcltk/tk8.6/mkpsenc.tcl', 'DATA'), + ('_tcl_data/msgs/ko.msg', '/usr/share/tcltk/tcl8.6/msgs/ko.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-5.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-5.enc', + 'DATA'), + ('base_library.zip', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/base_library.zip', + 'DATA')], + [('warnings', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/warnings.py', + 'PYMODULE'), + ('abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/abc.py', + 'PYMODULE'), + ('re._parser', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/re/_parser.py', + 'PYMODULE'), + ('re._constants', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/re/_constants.py', + 'PYMODULE'), + ('re._compiler', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/re/_compiler.py', + 'PYMODULE'), + ('re._casefix', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/re/_casefix.py', + 'PYMODULE'), + ('sre_constants', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/sre_constants.py', + 'PYMODULE'), + ('_collections_abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_collections_abc.py', + 'PYMODULE'), + ('posixpath', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/posixpath.py', + 'PYMODULE'), + ('collections.abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/collections/abc.py', + 'PYMODULE'), + ('collections', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/collections/__init__.py', + 'PYMODULE'), + ('sre_parse', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/sre_parse.py', + 'PYMODULE'), + ('types', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/types.py', + 'PYMODULE'), + ('weakref', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/weakref.py', + 'PYMODULE'), + ('io', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/io.py', + 'PYMODULE'), + ('linecache', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/linecache.py', + 'PYMODULE'), + ('enum', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/enum.py', + 'PYMODULE'), + ('traceback', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/traceback.py', + 'PYMODULE'), + ('keyword', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/keyword.py', + 'PYMODULE'), + ('operator', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/operator.py', + 'PYMODULE'), + ('functools', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/functools.py', + 'PYMODULE'), + ('encodings.zlib_codec', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/zlib_codec.py', + 'PYMODULE'), + ('encodings.uu_codec', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/uu_codec.py', + 'PYMODULE'), + ('encodings.utf_8_sig', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/utf_8_sig.py', + 'PYMODULE'), + ('encodings.utf_8', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/utf_8.py', + 'PYMODULE'), + ('encodings.utf_7', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/utf_7.py', + 'PYMODULE'), + ('encodings.utf_32_le', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/utf_32_le.py', + 'PYMODULE'), + ('encodings.utf_32_be', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/utf_32_be.py', + 'PYMODULE'), + ('encodings.utf_32', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/utf_32.py', + 'PYMODULE'), + ('encodings.utf_16_le', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/utf_16_le.py', + 'PYMODULE'), + ('encodings.utf_16_be', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/utf_16_be.py', + 'PYMODULE'), + ('encodings.utf_16', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/utf_16.py', + 'PYMODULE'), + ('encodings.unicode_escape', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/unicode_escape.py', + 'PYMODULE'), + ('encodings.undefined', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/undefined.py', + 'PYMODULE'), + ('encodings.tis_620', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/tis_620.py', + 'PYMODULE'), + ('encodings.shift_jisx0213', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/shift_jisx0213.py', + 'PYMODULE'), + ('encodings.shift_jis_2004', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/shift_jis_2004.py', + 'PYMODULE'), + ('encodings.shift_jis', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/shift_jis.py', + 'PYMODULE'), + ('encodings.rot_13', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/rot_13.py', + 'PYMODULE'), + ('encodings.raw_unicode_escape', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/raw_unicode_escape.py', + 'PYMODULE'), + ('encodings.quopri_codec', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/quopri_codec.py', + 'PYMODULE'), + ('encodings.punycode', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/punycode.py', + 'PYMODULE'), + ('encodings.ptcp154', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/ptcp154.py', + 'PYMODULE'), + ('encodings.palmos', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/palmos.py', + 'PYMODULE'), + ('encodings.oem', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/oem.py', + 'PYMODULE'), + ('encodings.mbcs', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mbcs.py', + 'PYMODULE'), + ('encodings.mac_turkish', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_turkish.py', + 'PYMODULE'), + ('encodings.mac_romanian', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_romanian.py', + 'PYMODULE'), + ('encodings.mac_roman', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_roman.py', + 'PYMODULE'), + ('encodings.mac_latin2', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_latin2.py', + 'PYMODULE'), + ('encodings.mac_iceland', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_iceland.py', + 'PYMODULE'), + ('encodings.mac_greek', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_greek.py', + 'PYMODULE'), + ('encodings.mac_farsi', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_farsi.py', + 'PYMODULE'), + ('encodings.mac_cyrillic', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_cyrillic.py', + 'PYMODULE'), + ('encodings.mac_croatian', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_croatian.py', + 'PYMODULE'), + ('encodings.mac_arabic', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/mac_arabic.py', + 'PYMODULE'), + ('encodings.latin_1', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/latin_1.py', + 'PYMODULE'), + ('encodings.kz1048', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/kz1048.py', + 'PYMODULE'), + ('encodings.koi8_u', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/koi8_u.py', + 'PYMODULE'), + ('encodings.koi8_t', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/koi8_t.py', + 'PYMODULE'), + ('encodings.koi8_r', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/koi8_r.py', + 'PYMODULE'), + ('encodings.johab', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/johab.py', + 'PYMODULE'), + ('encodings.iso8859_9', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_9.py', + 'PYMODULE'), + ('encodings.iso8859_8', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_8.py', + 'PYMODULE'), + ('encodings.iso8859_7', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_7.py', + 'PYMODULE'), + ('encodings.iso8859_6', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_6.py', + 'PYMODULE'), + ('encodings.iso8859_5', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_5.py', + 'PYMODULE'), + ('encodings.iso8859_4', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_4.py', + 'PYMODULE'), + ('encodings.iso8859_3', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_3.py', + 'PYMODULE'), + ('encodings.iso8859_2', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_2.py', + 'PYMODULE'), + ('encodings.iso8859_16', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_16.py', + 'PYMODULE'), + ('encodings.iso8859_15', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_15.py', + 'PYMODULE'), + ('encodings.iso8859_14', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_14.py', + 'PYMODULE'), + ('encodings.iso8859_13', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_13.py', + 'PYMODULE'), + ('encodings.iso8859_11', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_11.py', + 'PYMODULE'), + ('encodings.iso8859_10', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_10.py', + 'PYMODULE'), + ('encodings.iso8859_1', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso8859_1.py', + 'PYMODULE'), + ('encodings.iso2022_kr', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso2022_kr.py', + 'PYMODULE'), + ('encodings.iso2022_jp_ext', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso2022_jp_ext.py', + 'PYMODULE'), + ('encodings.iso2022_jp_3', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso2022_jp_3.py', + 'PYMODULE'), + ('encodings.iso2022_jp_2004', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso2022_jp_2004.py', + 'PYMODULE'), + ('encodings.iso2022_jp_2', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso2022_jp_2.py', + 'PYMODULE'), + ('encodings.iso2022_jp_1', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso2022_jp_1.py', + 'PYMODULE'), + ('encodings.iso2022_jp', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/iso2022_jp.py', + 'PYMODULE'), + ('encodings.idna', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/idna.py', + 'PYMODULE'), + ('encodings.hz', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/hz.py', + 'PYMODULE'), + ('encodings.hp_roman8', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/hp_roman8.py', + 'PYMODULE'), + ('encodings.hex_codec', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/hex_codec.py', + 'PYMODULE'), + ('encodings.gbk', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/gbk.py', + 'PYMODULE'), + ('encodings.gb2312', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/gb2312.py', + 'PYMODULE'), + ('encodings.gb18030', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/gb18030.py', + 'PYMODULE'), + ('encodings.euc_kr', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/euc_kr.py', + 'PYMODULE'), + ('encodings.euc_jp', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/euc_jp.py', + 'PYMODULE'), + ('encodings.euc_jisx0213', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/euc_jisx0213.py', + 'PYMODULE'), + ('encodings.euc_jis_2004', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/euc_jis_2004.py', + 'PYMODULE'), + ('encodings.cp950', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp950.py', + 'PYMODULE'), + ('encodings.cp949', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp949.py', + 'PYMODULE'), + ('encodings.cp932', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp932.py', + 'PYMODULE'), + ('encodings.cp875', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp875.py', + 'PYMODULE'), + ('encodings.cp874', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp874.py', + 'PYMODULE'), + ('encodings.cp869', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp869.py', + 'PYMODULE'), + ('encodings.cp866', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp866.py', + 'PYMODULE'), + ('encodings.cp865', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp865.py', + 'PYMODULE'), + ('encodings.cp864', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp864.py', + 'PYMODULE'), + ('encodings.cp863', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp863.py', + 'PYMODULE'), + ('encodings.cp862', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp862.py', + 'PYMODULE'), + ('encodings.cp861', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp861.py', + 'PYMODULE'), + ('encodings.cp860', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp860.py', + 'PYMODULE'), + ('encodings.cp858', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp858.py', + 'PYMODULE'), + ('encodings.cp857', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp857.py', + 'PYMODULE'), + ('encodings.cp856', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp856.py', + 'PYMODULE'), + ('encodings.cp855', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp855.py', + 'PYMODULE'), + ('encodings.cp852', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp852.py', + 'PYMODULE'), + ('encodings.cp850', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp850.py', + 'PYMODULE'), + ('encodings.cp775', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp775.py', + 'PYMODULE'), + ('encodings.cp737', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp737.py', + 'PYMODULE'), + ('encodings.cp720', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp720.py', + 'PYMODULE'), + ('encodings.cp500', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp500.py', + 'PYMODULE'), + ('encodings.cp437', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp437.py', + 'PYMODULE'), + ('encodings.cp424', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp424.py', + 'PYMODULE'), + ('encodings.cp273', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp273.py', + 'PYMODULE'), + ('encodings.cp1258', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1258.py', + 'PYMODULE'), + ('encodings.cp1257', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1257.py', + 'PYMODULE'), + ('encodings.cp1256', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1256.py', + 'PYMODULE'), + ('encodings.cp1255', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1255.py', + 'PYMODULE'), + ('encodings.cp1254', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1254.py', + 'PYMODULE'), + ('encodings.cp1253', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1253.py', + 'PYMODULE'), + ('encodings.cp1252', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1252.py', + 'PYMODULE'), + ('encodings.cp1251', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1251.py', + 'PYMODULE'), + ('encodings.cp1250', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1250.py', + 'PYMODULE'), + ('encodings.cp1140', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1140.py', + 'PYMODULE'), + ('encodings.cp1125', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1125.py', + 'PYMODULE'), + ('encodings.cp1026', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1026.py', + 'PYMODULE'), + ('encodings.cp1006', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp1006.py', + 'PYMODULE'), + ('encodings.cp037', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/cp037.py', + 'PYMODULE'), + ('encodings.charmap', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/charmap.py', + 'PYMODULE'), + ('encodings.bz2_codec', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/bz2_codec.py', + 'PYMODULE'), + ('encodings.big5hkscs', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/big5hkscs.py', + 'PYMODULE'), + ('encodings.big5', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/big5.py', + 'PYMODULE'), + ('encodings.base64_codec', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/base64_codec.py', + 'PYMODULE'), + ('encodings.ascii', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/ascii.py', + 'PYMODULE'), + ('encodings.aliases', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/aliases.py', + 'PYMODULE'), + ('encodings', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/encodings/__init__.py', + 'PYMODULE'), + ('heapq', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/heapq.py', + 'PYMODULE'), + ('locale', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/locale.py', + 'PYMODULE'), + ('genericpath', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/genericpath.py', + 'PYMODULE'), + ('copyreg', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/copyreg.py', + 'PYMODULE'), + ('sre_compile', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/sre_compile.py', + 'PYMODULE'), + ('codecs', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/codecs.py', + 'PYMODULE'), + ('ntpath', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/ntpath.py', + 'PYMODULE'), + ('_weakrefset', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_weakrefset.py', + 'PYMODULE'), + ('reprlib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/reprlib.py', + 'PYMODULE'), + ('stat', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/stat.py', + 'PYMODULE'), + ('re', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/re/__init__.py', + 'PYMODULE'), + ('os', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/os.py', + 'PYMODULE')]) diff --git a/build/pointcab_renamer/EXE-00.toc b/build/pointcab_renamer/EXE-00.toc new file mode 100644 index 0000000..4a07df2 --- /dev/null +++ b/build/pointcab_renamer/EXE-00.toc @@ -0,0 +1,913 @@ +('/home/ubuntu/pointcab_renamer/dist/pointcab_renamer', + True, + False, + False, + None, + None, + False, + False, + None, + True, + False, + None, + None, + None, + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/pointcab_renamer.pkg', + [('pyi-contents-directory _internal', '', 'OPTION'), + ('PYZ-00.pyz', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/PYZ-00.pyz', + 'PYZ'), + ('python3.11/lib-dynload/_struct.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_struct.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/zlib.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/zlib.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('struct', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/localpycs/struct.pyc', + 'PYMODULE'), + ('pyimod01_archive', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/localpycs/pyimod01_archive.pyc', + 'PYMODULE'), + ('pyimod02_importers', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/localpycs/pyimod02_importers.pyc', + 'PYMODULE'), + ('pyimod03_ctypes', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/localpycs/pyimod03_ctypes.pyc', + 'PYMODULE'), + ('pyiboot01_bootstrap', + '/home/ubuntu/.local/lib/python3.11/site-packages/PyInstaller/loader/pyiboot01_bootstrap.py', + 'PYSOURCE'), + ('pyi_rth_inspect', + '/home/ubuntu/.local/lib/python3.11/site-packages/PyInstaller/hooks/rthooks/pyi_rth_inspect.py', + 'PYSOURCE'), + ('pyi_rth__tkinter', + '/home/ubuntu/.local/lib/python3.11/site-packages/PyInstaller/hooks/rthooks/pyi_rth__tkinter.py', + 'PYSOURCE'), + ('pointcab_renamer', + '/home/ubuntu/pointcab_renamer/pointcab_renamer.py', + 'PYSOURCE'), + ('libpython3.11.so.1.0', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/libpython3.11.so.1.0', + 'BINARY'), + ('python3.11/lib-dynload/_typing.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_typing.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_statistics.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_statistics.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_contextvars.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_contextvars.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_decimal.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_decimal.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_hashlib.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_hashlib.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha3.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha3.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_blake2.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_blake2.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha256.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha256.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_md5.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_md5.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha1.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha1.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha512.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha512.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_random.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_random.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_bisect.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_bisect.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/math.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/math.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/unicodedata.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/unicodedata.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/array.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/array.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/select.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/select.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_socket.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_socket.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_csv.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_csv.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/resource.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/resource.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_lzma.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_lzma.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_bz2.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_bz2.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/binascii.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/binascii.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_opcode.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_opcode.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_pickle.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_pickle.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_multibytecodec.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_multibytecodec.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_jp.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_jp.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_kr.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_kr.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_iso2022.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_iso2022.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_cn.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_cn.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_tw.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_tw.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_hk.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_hk.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_heapq.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_heapq.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_uuid.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_uuid.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/grp.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/grp.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_posixsubprocess.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_posixsubprocess.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/fcntl.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/fcntl.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_elementtree.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_elementtree.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/pyexpat.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/pyexpat.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/termios.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/termios.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_ssl.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_ssl.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_datetime.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_datetime.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_tkinter.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_tkinter.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('libcrypto.so.3', '/lib/x86_64-linux-gnu/libcrypto.so.3', 'BINARY'), + ('liblzma.so.5', '/lib/x86_64-linux-gnu/liblzma.so.5', 'BINARY'), + ('libbz2.so.1.0', '/lib/x86_64-linux-gnu/libbz2.so.1.0', 'BINARY'), + ('libz.so.1', '/lib/x86_64-linux-gnu/libz.so.1', 'BINARY'), + ('libuuid.so.1', '/lib/x86_64-linux-gnu/libuuid.so.1', 'BINARY'), + ('libssl.so.3', '/lib/x86_64-linux-gnu/libssl.so.3', 'BINARY'), + ('libfreetype.so.6', '/lib/x86_64-linux-gnu/libfreetype.so.6', 'BINARY'), + ('libXau.so.6', '/lib/x86_64-linux-gnu/libXau.so.6', 'BINARY'), + ('libbrotlicommon.so.1', + '/lib/x86_64-linux-gnu/libbrotlicommon.so.1', + 'BINARY'), + ('libtk8.6.so', '/lib/x86_64-linux-gnu/libtk8.6.so', 'BINARY'), + ('libtcl8.6.so', '/lib/x86_64-linux-gnu/libtcl8.6.so', 'BINARY'), + ('libmd.so.0', '/lib/x86_64-linux-gnu/libmd.so.0', 'BINARY'), + ('libexpat.so.1', '/lib/x86_64-linux-gnu/libexpat.so.1', 'BINARY'), + ('libXext.so.6', '/lib/x86_64-linux-gnu/libXext.so.6', 'BINARY'), + ('libXdmcp.so.6', '/lib/x86_64-linux-gnu/libXdmcp.so.6', 'BINARY'), + ('libbrotlidec.so.1', '/lib/x86_64-linux-gnu/libbrotlidec.so.1', 'BINARY'), + ('libpng16.so.16', '/lib/x86_64-linux-gnu/libpng16.so.16', 'BINARY'), + ('libfontconfig.so.1', '/lib/x86_64-linux-gnu/libfontconfig.so.1', 'BINARY'), + ('libXrender.so.1', '/lib/x86_64-linux-gnu/libXrender.so.1', 'BINARY'), + ('libXft.so.2', '/lib/x86_64-linux-gnu/libXft.so.2', 'BINARY'), + ('libX11.so.6', '/lib/x86_64-linux-gnu/libX11.so.6', 'BINARY'), + ('libbsd.so.0', '/lib/x86_64-linux-gnu/libbsd.so.0', 'BINARY'), + ('libXss.so.1', '/lib/x86_64-linux-gnu/libXss.so.1', 'BINARY'), + ('cluster_cleanup.txt', + '/home/ubuntu/pointcab_renamer/cluster_cleanup.txt', + 'DATA'), + ('_tcl_data/tcl8/tcltest-2.5.5.tm', + '/usr/share/tcltk/tcl8.6/tcl8/tcltest-2.5.5.tm', + 'DATA'), + ('_tk_data/choosedir.tcl', '/usr/share/tcltk/tk8.6/choosedir.tcl', 'DATA'), + ('_tcl_data/msgs/fr_ca.msg', + '/usr/share/tcltk/tcl8.6/msgs/fr_ca.msg', + 'DATA'), + ('_tcl_data/msgs/fi.msg', '/usr/share/tcltk/tcl8.6/msgs/fi.msg', 'DATA'), + ('_tcl_data/encoding/cp874.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp874.enc', + 'DATA'), + ('_tk_data/ttk/ttk.tcl', '/usr/share/tcltk/tk8.6/ttk/ttk.tcl', 'DATA'), + ('_tcl_data/msgs/bn_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/bn_in.msg', + 'DATA'), + ('_tcl_data/encoding/cp850.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp850.enc', + 'DATA'), + ('_tcl_data/msgs/hu.msg', '/usr/share/tcltk/tcl8.6/msgs/hu.msg', 'DATA'), + ('_tk_data/dialog.tcl', '/usr/share/tcltk/tk8.6/dialog.tcl', 'DATA'), + ('_tk_data/ttk/sizegrip.tcl', + '/usr/share/tcltk/tk8.6/ttk/sizegrip.tcl', + 'DATA'), + ('_tcl_data/msgs/mk.msg', '/usr/share/tcltk/tcl8.6/msgs/mk.msg', 'DATA'), + ('_tk_data/images/logoMed.gif', + '/usr/share/tcltk/tk8.6/images/logoMed.gif', + 'DATA'), + ('_tcl_data/msgs/sq.msg', '/usr/share/tcltk/tcl8.6/msgs/sq.msg', 'DATA'), + ('_tk_data/ttk/vistaTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/vistaTheme.tcl', + 'DATA'), + ('_tk_data/msgs/pl.msg', '/usr/share/tcltk/tk8.6/msgs/pl.msg', 'DATA'), + ('_tk_data/ttk/scrollbar.tcl', + '/usr/share/tcltk/tk8.6/ttk/scrollbar.tcl', + 'DATA'), + ('_tk_data/panedwindow.tcl', + '/usr/share/tcltk/tk8.6/panedwindow.tcl', + 'DATA'), + ('_tcl_data/encoding/iso2022.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso2022.enc', + 'DATA'), + ('_tcl_data/encoding/euc-jp.enc', + '/usr/share/tcltk/tcl8.6/encoding/euc-jp.enc', + 'DATA'), + ('_tcl_data/tcl8/msgcat-1.6.1.tm', + '/usr/share/tcltk/tcl8.6/tcl8/msgcat-1.6.1.tm', + 'DATA'), + ('_tcl_data/encoding/dingbats.enc', + '/usr/share/tcltk/tcl8.6/encoding/dingbats.enc', + 'DATA'), + ('_tcl_data/encoding/macRoman.enc', + '/usr/share/tcltk/tcl8.6/encoding/macRoman.enc', + 'DATA'), + ('_tk_data/tkAppInit.c', '/usr/share/tcltk/tk8.6/tkAppInit.c', 'DATA'), + ('_tk_data/ttk/combobox.tcl', + '/usr/share/tcltk/tk8.6/ttk/combobox.tcl', + 'DATA'), + ('_tcl_data/msgs/fr.msg', '/usr/share/tcltk/tcl8.6/msgs/fr.msg', 'DATA'), + ('_tcl_data/tclIndex', '/usr/share/tcltk/tcl8.6/tclIndex', 'DATA'), + ('_tk_data/msgs/en.msg', '/usr/share/tcltk/tk8.6/msgs/en.msg', 'DATA'), + ('_tcl_data/msgs/en_ph.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_ph.msg', + 'DATA'), + ('_tk_data/images/pwrdLogo75.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo75.gif', + 'DATA'), + ('_tcl_data/msgs/zh_cn.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_cn.msg', + 'DATA'), + ('_tcl_data/encoding/cp866.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp866.enc', + 'DATA'), + ('_tcl_data/msgs/mr.msg', '/usr/share/tcltk/tcl8.6/msgs/mr.msg', 'DATA'), + ('_tk_data/ttk/winTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/winTheme.tcl', + 'DATA'), + ('_tcl_data/msgs/es_hn.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_hn.msg', + 'DATA'), + ('_tcl_data/msgs/th.msg', '/usr/share/tcltk/tcl8.6/msgs/th.msg', 'DATA'), + ('_tk_data/msgs/hu.msg', '/usr/share/tcltk/tk8.6/msgs/hu.msg', 'DATA'), + ('_tk_data/images/pwrdLogo175.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo175.gif', + 'DATA'), + ('_tcl_data/encoding/iso8859-9.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-9.enc', + 'DATA'), + ('_tk_data/tkfbox.tcl', '/usr/share/tcltk/tk8.6/tkfbox.tcl', 'DATA'), + ('_tcl_data/msgs/sk.msg', '/usr/share/tcltk/tcl8.6/msgs/sk.msg', 'DATA'), + ('_tcl_data/encoding/macUkraine.enc', + '/usr/share/tcltk/tcl8.6/encoding/macUkraine.enc', + 'DATA'), + ('_tcl_data/encoding/ebcdic.enc', + '/usr/share/tcltk/tcl8.6/encoding/ebcdic.enc', + 'DATA'), + ('_tk_data/ttk/clamTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/clamTheme.tcl', + 'DATA'), + ('_tcl_data/encoding/macJapan.enc', + '/usr/share/tcltk/tcl8.6/encoding/macJapan.enc', + 'DATA'), + ('_tk_data/msgs/fi.msg', '/usr/share/tcltk/tk8.6/msgs/fi.msg', 'DATA'), + ('_tcl_data/tm.tcl', '/usr/share/tcltk/tcl8.6/tm.tcl', 'DATA'), + ('_tk_data/images/logoLarge.gif', + '/usr/share/tcltk/tk8.6/images/logoLarge.gif', + 'DATA'), + ('_tcl_data/msgs/ja.msg', '/usr/share/tcltk/tcl8.6/msgs/ja.msg', 'DATA'), + ('_tcl_data/msgs/gl.msg', '/usr/share/tcltk/tcl8.6/msgs/gl.msg', 'DATA'), + ('_tk_data/safetk.tcl', '/usr/share/tcltk/tk8.6/safetk.tcl', 'DATA'), + ('_tcl_data/msgs/kok.msg', '/usr/share/tcltk/tcl8.6/msgs/kok.msg', 'DATA'), + ('_tcl_data/msgs/mr_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/mr_in.msg', + 'DATA'), + ('_tk_data/iconlist.tcl', '/usr/share/tcltk/tk8.6/iconlist.tcl', 'DATA'), + ('_tcl_data/msgs/es_ni.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ni.msg', + 'DATA'), + ('_tcl_data/encoding/macRomania.enc', + '/usr/share/tcltk/tcl8.6/encoding/macRomania.enc', + 'DATA'), + ('_tk_data/fontchooser.tcl', + '/usr/share/tcltk/tk8.6/fontchooser.tcl', + 'DATA'), + ('_tk_data/ttk/entry.tcl', '/usr/share/tcltk/tk8.6/ttk/entry.tcl', 'DATA'), + ('_tcl_data/encoding/cp737.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp737.enc', + 'DATA'), + ('_tcl_data/msgs/zh_tw.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_tw.msg', + 'DATA'), + ('_tcl_data/msgs/es_co.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_co.msg', + 'DATA'), + ('_tcl_data/msgs/pl.msg', '/usr/share/tcltk/tcl8.6/msgs/pl.msg', 'DATA'), + ('_tk_data/images/logo64.gif', + '/usr/share/tcltk/tk8.6/images/logo64.gif', + 'DATA'), + ('_tcl_data/msgs/sl.msg', '/usr/share/tcltk/tcl8.6/msgs/sl.msg', 'DATA'), + ('_tk_data/msgs/en_gb.msg', '/usr/share/tcltk/tk8.6/msgs/en_gb.msg', 'DATA'), + ('_tcl_data/msgs/kl.msg', '/usr/share/tcltk/tcl8.6/msgs/kl.msg', 'DATA'), + ('_tcl_data/encoding/cp864.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp864.enc', + 'DATA'), + ('_tcl_data/msgs/lv.msg', '/usr/share/tcltk/tcl8.6/msgs/lv.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-8.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-8.enc', + 'DATA'), + ('_tcl_data/msgs/de_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/de_be.msg', + 'DATA'), + ('_tcl_data/encoding/cp936.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp936.enc', + 'DATA'), + ('_tcl_data/msgs/hr.msg', '/usr/share/tcltk/tcl8.6/msgs/hr.msg', 'DATA'), + ('_tcl_data/encoding/big5.enc', + '/usr/share/tcltk/tcl8.6/encoding/big5.enc', + 'DATA'), + ('_tcl_data/encoding/macIceland.enc', + '/usr/share/tcltk/tcl8.6/encoding/macIceland.enc', + 'DATA'), + ('_tcl_data/history.tcl', '/usr/share/tcltk/tcl8.6/history.tcl', 'DATA'), + ('_tcl_data/msgs/hi.msg', '/usr/share/tcltk/tcl8.6/msgs/hi.msg', 'DATA'), + ('_tcl_data/msgs/kw.msg', '/usr/share/tcltk/tcl8.6/msgs/kw.msg', 'DATA'), + ('_tcl_data/msgs/en_au.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_au.msg', + 'DATA'), + ('_tk_data/ttk/scale.tcl', '/usr/share/tcltk/tk8.6/ttk/scale.tcl', 'DATA'), + ('_tcl_data/msgs/fa_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/fa_in.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-4.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-4.enc', + 'DATA'), + ('_tcl_data/msgs/ru_ua.msg', + '/usr/share/tcltk/tcl8.6/msgs/ru_ua.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-6.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-6.enc', + 'DATA'), + ('_tcl_data/encoding/cp1251.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1251.enc', + 'DATA'), + ('_tcl_data/msgs/es_pe.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_pe.msg', + 'DATA'), + ('_tcl_data/opt0.4/pkgIndex.tcl', + '/usr/share/tcltk/tcl8.6/opt0.4/pkgIndex.tcl', + 'DATA'), + ('_tcl_data/msgs/et.msg', '/usr/share/tcltk/tcl8.6/msgs/et.msg', 'DATA'), + ('_tcl_data/msgs/kl_gl.msg', + '/usr/share/tcltk/tcl8.6/msgs/kl_gl.msg', + 'DATA'), + ('_tcl_data/encoding/cp1256.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1256.enc', + 'DATA'), + ('_tcl_data/encoding/jis0201.enc', + '/usr/share/tcltk/tcl8.6/encoding/jis0201.enc', + 'DATA'), + ('_tk_data/text.tcl', '/usr/share/tcltk/tk8.6/text.tcl', 'DATA'), + ('_tk_data/images/tai-ku.gif', + '/usr/share/tcltk/tk8.6/images/tai-ku.gif', + 'DATA'), + ('_tcl_data/msgs/kok_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/kok_in.msg', + 'DATA'), + ('_tk_data/ttk/xpTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/xpTheme.tcl', + 'DATA'), + ('_tk_data/icons.tcl', '/usr/share/tcltk/tk8.6/icons.tcl', 'DATA'), + ('_tcl_data/msgs/sv.msg', '/usr/share/tcltk/tcl8.6/msgs/sv.msg', 'DATA'), + ('_tk_data/ttk/aquaTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/aquaTheme.tcl', + 'DATA'), + ('_tcl_data/msgs/lt.msg', '/usr/share/tcltk/tcl8.6/msgs/lt.msg', 'DATA'), + ('_tcl_data/msgs/fo_fo.msg', + '/usr/share/tcltk/tcl8.6/msgs/fo_fo.msg', + 'DATA'), + ('_tcl_data/encoding/macCentEuro.enc', + '/usr/share/tcltk/tcl8.6/encoding/macCentEuro.enc', + 'DATA'), + ('_tcl_data/msgs/en_bw.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_bw.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-14.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-14.enc', + 'DATA'), + ('_tk_data/ttk/altTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/altTheme.tcl', + 'DATA'), + ('_tcl_data/encoding/gb2312.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb2312.enc', + 'DATA'), + ('_tcl_data/encoding/cp1252.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1252.enc', + 'DATA'), + ('_tcl_data/encoding/cp1255.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1255.enc', + 'DATA'), + ('_tk_data/palette.tcl', '/usr/share/tcltk/tk8.6/palette.tcl', 'DATA'), + ('_tcl_data/msgs/eo.msg', '/usr/share/tcltk/tcl8.6/msgs/eo.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-13.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-13.enc', + 'DATA'), + ('_tcl_data/msgs/nl.msg', '/usr/share/tcltk/tcl8.6/msgs/nl.msg', 'DATA'), + ('_tcl_data/msgs/en_ie.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_ie.msg', + 'DATA'), + ('_tcl_data/msgs/es_bo.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_bo.msg', + 'DATA'), + ('_tcl_data/msgs/ta.msg', '/usr/share/tcltk/tcl8.6/msgs/ta.msg', 'DATA'), + ('_tcl_data/encoding/euc-kr.enc', + '/usr/share/tcltk/tcl8.6/encoding/euc-kr.enc', + 'DATA'), + ('_tk_data/scale.tcl', '/usr/share/tcltk/tk8.6/scale.tcl', 'DATA'), + ('_tcl_data/tclAppInit.c', '/usr/share/tcltk/tcl8.6/tclAppInit.c', 'DATA'), + ('_tcl_data/encoding/iso2022-kr.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso2022-kr.enc', + 'DATA'), + ('_tcl_data/msgs/ar_jo.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_jo.msg', + 'DATA'), + ('_tcl_data/msgs/en_gb.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_gb.msg', + 'DATA'), + ('_tcl_data/msgs/sh.msg', '/usr/share/tcltk/tcl8.6/msgs/sh.msg', 'DATA'), + ('_tcl_data/msgs/da.msg', '/usr/share/tcltk/tcl8.6/msgs/da.msg', 'DATA'), + ('_tcl_data/msgs/nl_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/nl_be.msg', + 'DATA'), + ('_tcl_data/encoding/cp852.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp852.enc', + 'DATA'), + ('_tcl_data/msgs/be.msg', '/usr/share/tcltk/tcl8.6/msgs/be.msg', 'DATA'), + ('_tk_data/msgs/nl.msg', '/usr/share/tcltk/tk8.6/msgs/nl.msg', 'DATA'), + ('_tcl_data/msgs/fa_ir.msg', + '/usr/share/tcltk/tcl8.6/msgs/fa_ir.msg', + 'DATA'), + ('_tcl_data/msgs/it.msg', '/usr/share/tcltk/tcl8.6/msgs/it.msg', 'DATA'), + ('_tk_data/ttk/cursors.tcl', + '/usr/share/tcltk/tk8.6/ttk/cursors.tcl', + 'DATA'), + ('_tcl_data/msgs/zh_sg.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_sg.msg', + 'DATA'), + ('_tk_data/spinbox.tcl', '/usr/share/tcltk/tk8.6/spinbox.tcl', 'DATA'), + ('_tk_data/msgs/es.msg', '/usr/share/tcltk/tk8.6/msgs/es.msg', 'DATA'), + ('_tk_data/msgs/el.msg', '/usr/share/tcltk/tk8.6/msgs/el.msg', 'DATA'), + ('_tcl_data/msgs/ar_sy.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_sy.msg', + 'DATA'), + ('_tcl_data/msgs/ar.msg', '/usr/share/tcltk/tcl8.6/msgs/ar.msg', 'DATA'), + ('_tcl_data/msgs/ga_ie.msg', + '/usr/share/tcltk/tcl8.6/msgs/ga_ie.msg', + 'DATA'), + ('_tk_data/msgs/sv.msg', '/usr/share/tcltk/tk8.6/msgs/sv.msg', 'DATA'), + ('_tk_data/tclIndex', '/usr/share/tcltk/tk8.6/tclIndex', 'DATA'), + ('_tcl_data/msgs/uk.msg', '/usr/share/tcltk/tcl8.6/msgs/uk.msg', 'DATA'), + ('_tcl_data/word.tcl', '/usr/share/tcltk/tcl8.6/word.tcl', 'DATA'), + ('_tcl_data/msgs/ga.msg', '/usr/share/tcltk/tcl8.6/msgs/ga.msg', 'DATA'), + ('_tcl_data/msgs/en_za.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_za.msg', + 'DATA'), + ('_tcl_data/msgs/fr_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/fr_be.msg', + 'DATA'), + ('_tk_data/msgbox.tcl', '/usr/share/tcltk/tk8.6/msgbox.tcl', 'DATA'), + ('_tcl_data/encoding/cp1257.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1257.enc', + 'DATA'), + ('_tcl_data/encoding/iso8859-3.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-3.enc', + 'DATA'), + ('_tcl_data/encoding/euc-cn.enc', + '/usr/share/tcltk/tcl8.6/encoding/euc-cn.enc', + 'DATA'), + ('_tcl_data/msgs/id.msg', '/usr/share/tcltk/tcl8.6/msgs/id.msg', 'DATA'), + ('_tk_data/ttk/menubutton.tcl', + '/usr/share/tcltk/tk8.6/ttk/menubutton.tcl', + 'DATA'), + ('_tk_data/tearoff.tcl', '/usr/share/tcltk/tk8.6/tearoff.tcl', 'DATA'), + ('_tk_data/optMenu.tcl', '/usr/share/tcltk/tk8.6/optMenu.tcl', 'DATA'), + ('_tcl_data/tcl8/platform/shell-1.1.4.tm', + '/usr/share/tcltk/tcl8.6/tcl8/platform/shell-1.1.4.tm', + 'DATA'), + ('_tcl_data/encoding/cp860.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp860.enc', + 'DATA'), + ('_tcl_data/encoding/macCroatian.enc', + '/usr/share/tcltk/tcl8.6/encoding/macCroatian.enc', + 'DATA'), + ('_tcl_data/encoding/cp1253.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1253.enc', + 'DATA'), + ('_tcl_data/msgs/gv.msg', '/usr/share/tcltk/tcl8.6/msgs/gv.msg', 'DATA'), + ('_tcl_data/init.tcl', '/usr/share/tcltk/tcl8.6/init.tcl', 'DATA'), + ('_tcl_data/msgs/fr_ch.msg', + '/usr/share/tcltk/tcl8.6/msgs/fr_ch.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-10.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-10.enc', + 'DATA'), + ('_tcl_data/msgs/bg.msg', '/usr/share/tcltk/tcl8.6/msgs/bg.msg', 'DATA'), + ('_tcl_data/msgs/ro.msg', '/usr/share/tcltk/tcl8.6/msgs/ro.msg', 'DATA'), + ('_tcl_data/encoding/cp857.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp857.enc', + 'DATA'), + ('_tcl_data/msgs/gl_es.msg', + '/usr/share/tcltk/tcl8.6/msgs/gl_es.msg', + 'DATA'), + ('_tcl_data/msgs/es_uy.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_uy.msg', + 'DATA'), + ('_tk_data/ttk/panedwindow.tcl', + '/usr/share/tcltk/tk8.6/ttk/panedwindow.tcl', + 'DATA'), + ('_tcl_data/msgs/af.msg', '/usr/share/tcltk/tcl8.6/msgs/af.msg', 'DATA'), + ('_tcl_data/msgs/de_at.msg', + '/usr/share/tcltk/tcl8.6/msgs/de_at.msg', + 'DATA'), + ('_tcl_data/auto.tcl', '/usr/share/tcltk/tcl8.6/auto.tcl', 'DATA'), + ('_tcl_data/msgs/ms_my.msg', + '/usr/share/tcltk/tcl8.6/msgs/ms_my.msg', + 'DATA'), + ('_tcl_data/msgs/ms.msg', '/usr/share/tcltk/tcl8.6/msgs/ms.msg', 'DATA'), + ('_tcl_data/encoding/gb12345.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb12345.enc', + 'DATA'), + ('_tcl_data/msgs/ru.msg', '/usr/share/tcltk/tcl8.6/msgs/ru.msg', 'DATA'), + ('_tcl_data/msgs/sr.msg', '/usr/share/tcltk/tcl8.6/msgs/sr.msg', 'DATA'), + ('_tcl_data/msgs/zh.msg', '/usr/share/tcltk/tcl8.6/msgs/zh.msg', 'DATA'), + ('_tcl_data/msgs/mt.msg', '/usr/share/tcltk/tcl8.6/msgs/mt.msg', 'DATA'), + ('_tcl_data/encoding/jis0208.enc', + '/usr/share/tcltk/tcl8.6/encoding/jis0208.enc', + 'DATA'), + ('_tcl_data/encoding/iso8859-15.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-15.enc', + 'DATA'), + ('_tk_data/msgs/zh_cn.msg', '/usr/share/tcltk/tk8.6/msgs/zh_cn.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-16.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-16.enc', + 'DATA'), + ('_tcl_data/encoding/koi8-u.enc', + '/usr/share/tcltk/tcl8.6/encoding/koi8-u.enc', + 'DATA'), + ('_tk_data/unsupported.tcl', + '/usr/share/tcltk/tk8.6/unsupported.tcl', + 'DATA'), + ('_tcl_data/encoding/cp865.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp865.enc', + 'DATA'), + ('_tcl_data/msgs/es_ar.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ar.msg', + 'DATA'), + ('_tk_data/msgs/cs.msg', '/usr/share/tcltk/tk8.6/msgs/cs.msg', 'DATA'), + ('_tcl_data/encoding/koi8-r.enc', + '/usr/share/tcltk/tcl8.6/encoding/koi8-r.enc', + 'DATA'), + ('_tcl_data/parray.tcl', '/usr/share/tcltk/tcl8.6/parray.tcl', 'DATA'), + ('_tk_data/ttk/classicTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/classicTheme.tcl', + 'DATA'), + ('_tcl_data/encoding/cp1254.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1254.enc', + 'DATA'), + ('_tcl_data/encoding/symbol.enc', + '/usr/share/tcltk/tcl8.6/encoding/symbol.enc', + 'DATA'), + ('_tcl_data/msgs/sw.msg', '/usr/share/tcltk/tcl8.6/msgs/sw.msg', 'DATA'), + ('_tk_data/images/pwrdLogo.eps', + '/usr/share/tcltk/tk8.6/images/pwrdLogo.eps', + 'DATA'), + ('_tcl_data/msgs/es.msg', '/usr/share/tcltk/tcl8.6/msgs/es.msg', 'DATA'), + ('_tcl_data/package.tcl', '/usr/share/tcltk/tcl8.6/package.tcl', 'DATA'), + ('_tcl_data/encoding/macTurkish.enc', + '/usr/share/tcltk/tcl8.6/encoding/macTurkish.enc', + 'DATA'), + ('_tk_data/bgerror.tcl', '/usr/share/tcltk/tk8.6/bgerror.tcl', 'DATA'), + ('_tk_data/listbox.tcl', '/usr/share/tcltk/tk8.6/listbox.tcl', 'DATA'), + ('_tcl_data/msgs/es_mx.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_mx.msg', + 'DATA'), + ('_tk_data/msgs/it.msg', '/usr/share/tcltk/tk8.6/msgs/it.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-2.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-2.enc', + 'DATA'), + ('_tcl_data/encoding/cp1250.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1250.enc', + 'DATA'), + ('_tcl_data/encoding/macThai.enc', + '/usr/share/tcltk/tcl8.6/encoding/macThai.enc', + 'DATA'), + ('_tcl_data/encoding/macGreek.enc', + '/usr/share/tcltk/tcl8.6/encoding/macGreek.enc', + 'DATA'), + ('_tcl_data/opt0.4/optparse.tcl', + '/usr/share/tcltk/tcl8.6/opt0.4/optparse.tcl', + 'DATA'), + ('_tk_data/msgs/ru.msg', '/usr/share/tcltk/tk8.6/msgs/ru.msg', 'DATA'), + ('_tcl_data/encoding/ksc5601.enc', + '/usr/share/tcltk/tcl8.6/encoding/ksc5601.enc', + 'DATA'), + ('_tcl_data/encoding/jis0212.enc', + '/usr/share/tcltk/tcl8.6/encoding/jis0212.enc', + 'DATA'), + ('_tcl_data/encoding/tis-620.enc', + '/usr/share/tcltk/tcl8.6/encoding/tis-620.enc', + 'DATA'), + ('_tk_data/tk.tcl', '/usr/share/tcltk/tk8.6/tk.tcl', 'DATA'), + ('_tk_data/console.tcl', '/usr/share/tcltk/tk8.6/console.tcl', 'DATA'), + ('_tcl_data/msgs/it_ch.msg', + '/usr/share/tcltk/tcl8.6/msgs/it_ch.msg', + 'DATA'), + ('_tcl_data/encoding/macDingbats.enc', + '/usr/share/tcltk/tcl8.6/encoding/macDingbats.enc', + 'DATA'), + ('_tcl_data/encoding/cp437.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp437.enc', + 'DATA'), + ('_tcl_data/msgs/en_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_be.msg', + 'DATA'), + ('_tcl_data/tcl8/platform-1.0.19.tm', + '/usr/share/tcltk/tcl8.6/tcl8/platform-1.0.19.tm', + 'DATA'), + ('_tcl_data/msgs/pt_br.msg', + '/usr/share/tcltk/tcl8.6/msgs/pt_br.msg', + 'DATA'), + ('_tcl_data/clock.tcl', '/usr/share/tcltk/tcl8.6/clock.tcl', 'DATA'), + ('_tk_data/ttk/spinbox.tcl', + '/usr/share/tcltk/tk8.6/ttk/spinbox.tcl', + 'DATA'), + ('_tcl_data/encoding/iso8859-7.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-7.enc', + 'DATA'), + ('_tk_data/obsolete.tcl', '/usr/share/tcltk/tk8.6/obsolete.tcl', 'DATA'), + ('_tcl_data/tcl8/http-2.9.8.tm', + '/usr/share/tcltk/tcl8.6/tcl8/http-2.9.8.tm', + 'DATA'), + ('_tcl_data/msgs/es_ec.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ec.msg', + 'DATA'), + ('_tk_data/msgs/pt.msg', '/usr/share/tcltk/tk8.6/msgs/pt.msg', 'DATA'), + ('_tcl_data/encoding/cns11643.enc', + '/usr/share/tcltk/tcl8.6/encoding/cns11643.enc', + 'DATA'), + ('_tk_data/focus.tcl', '/usr/share/tcltk/tk8.6/focus.tcl', 'DATA'), + ('_tcl_data/msgs/is.msg', '/usr/share/tcltk/tcl8.6/msgs/is.msg', 'DATA'), + ('_tcl_data/msgs/en_hk.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_hk.msg', + 'DATA'), + ('_tcl_data/msgs/bn.msg', '/usr/share/tcltk/tcl8.6/msgs/bn.msg', 'DATA'), + ('_tcl_data/encoding/cp861.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp861.enc', + 'DATA'), + ('_tcl_data/msgs/kw_gb.msg', + '/usr/share/tcltk/tcl8.6/msgs/kw_gb.msg', + 'DATA'), + ('_tcl_data/encoding/cp869.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp869.enc', + 'DATA'), + ('_tcl_data/msgs/en_zw.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_zw.msg', + 'DATA'), + ('_tk_data/button.tcl', '/usr/share/tcltk/tk8.6/button.tcl', 'DATA'), + ('_tk_data/ttk/treeview.tcl', + '/usr/share/tcltk/tk8.6/ttk/treeview.tcl', + 'DATA'), + ('_tcl_data/msgs/es_cl.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_cl.msg', + 'DATA'), + ('_tk_data/ttk/defaults.tcl', + '/usr/share/tcltk/tk8.6/ttk/defaults.tcl', + 'DATA'), + ('_tcl_data/encoding/cp932.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp932.enc', + 'DATA'), + ('_tk_data/xmfbox.tcl', '/usr/share/tcltk/tk8.6/xmfbox.tcl', 'DATA'), + ('_tcl_data/msgs/el.msg', '/usr/share/tcltk/tcl8.6/msgs/el.msg', 'DATA'), + ('_tcl_data/msgs/fo.msg', '/usr/share/tcltk/tcl8.6/msgs/fo.msg', 'DATA'), + ('_tcl_data/encoding/cp949.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp949.enc', + 'DATA'), + ('_tcl_data/safe.tcl', '/usr/share/tcltk/tcl8.6/safe.tcl', 'DATA'), + ('_tk_data/images/pwrdLogo150.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo150.gif', + 'DATA'), + ('_tk_data/entry.tcl', '/usr/share/tcltk/tk8.6/entry.tcl', 'DATA'), + ('_tcl_data/msgs/vi.msg', '/usr/share/tcltk/tcl8.6/msgs/vi.msg', 'DATA'), + ('_tcl_data/http1.0/pkgIndex.tcl', + '/usr/share/tcltk/tcl8.6/http1.0/pkgIndex.tcl', + 'DATA'), + ('_tk_data/msgs/de.msg', '/usr/share/tcltk/tk8.6/msgs/de.msg', 'DATA'), + ('_tcl_data/encoding/cp855.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp855.enc', + 'DATA'), + ('_tcl_data/msgs/ar_lb.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_lb.msg', + 'DATA'), + ('_tcl_data/msgs/eu_es.msg', + '/usr/share/tcltk/tcl8.6/msgs/eu_es.msg', + 'DATA'), + ('_tcl_data/msgs/cs.msg', '/usr/share/tcltk/tcl8.6/msgs/cs.msg', 'DATA'), + ('_tcl_data/encoding/cp862.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp862.enc', + 'DATA'), + ('_tcl_data/msgs/pt.msg', '/usr/share/tcltk/tcl8.6/msgs/pt.msg', 'DATA'), + ('_tk_data/ttk/progress.tcl', + '/usr/share/tcltk/tk8.6/ttk/progress.tcl', + 'DATA'), + ('_tcl_data/encoding/gb2312-raw.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb2312-raw.enc', + 'DATA'), + ('_tk_data/clrpick.tcl', '/usr/share/tcltk/tk8.6/clrpick.tcl', 'DATA'), + ('_tcl_data/encoding/shiftjis.enc', + '/usr/share/tcltk/tcl8.6/encoding/shiftjis.enc', + 'DATA'), + ('_tcl_data/msgs/nb.msg', '/usr/share/tcltk/tcl8.6/msgs/nb.msg', 'DATA'), + ('_tcl_data/msgs/he.msg', '/usr/share/tcltk/tcl8.6/msgs/he.msg', 'DATA'), + ('_tk_data/msgs/fr.msg', '/usr/share/tcltk/tk8.6/msgs/fr.msg', 'DATA'), + ('_tk_data/comdlg.tcl', '/usr/share/tcltk/tk8.6/comdlg.tcl', 'DATA'), + ('_tcl_data/http1.0/http.tcl', + '/usr/share/tcltk/tcl8.6/http1.0/http.tcl', + 'DATA'), + ('_tcl_data/msgs/fa.msg', '/usr/share/tcltk/tcl8.6/msgs/fa.msg', 'DATA'), + ('_tcl_data/encoding/gb1988.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb1988.enc', + 'DATA'), + ('_tk_data/images/logo100.gif', + '/usr/share/tcltk/tk8.6/images/logo100.gif', + 'DATA'), + ('_tcl_data/msgs/es_gt.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_gt.msg', + 'DATA'), + ('_tcl_data/msgs/ta_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/ta_in.msg', + 'DATA'), + ('_tcl_data/encoding/cp863.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp863.enc', + 'DATA'), + ('_tcl_data/msgs/tr.msg', '/usr/share/tcltk/tcl8.6/msgs/tr.msg', 'DATA'), + ('_tcl_data/msgs/es_pr.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_pr.msg', + 'DATA'), + ('_tcl_data/msgs/ca.msg', '/usr/share/tcltk/tcl8.6/msgs/ca.msg', 'DATA'), + ('_tk_data/menu.tcl', '/usr/share/tcltk/tk8.6/menu.tcl', 'DATA'), + ('_tk_data/msgs/da.msg', '/usr/share/tcltk/tk8.6/msgs/da.msg', 'DATA'), + ('_tk_data/images/README', '/usr/share/tcltk/tk8.6/images/README', 'DATA'), + ('_tcl_data/encoding/iso8859-1.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-1.enc', + 'DATA'), + ('_tcl_data/msgs/es_py.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_py.msg', + 'DATA'), + ('_tk_data/ttk/notebook.tcl', + '/usr/share/tcltk/tk8.6/ttk/notebook.tcl', + 'DATA'), + ('_tcl_data/msgs/ar_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_in.msg', + 'DATA'), + ('_tcl_data/encoding/cp1258.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1258.enc', + 'DATA'), + ('_tcl_data/msgs/hi_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/hi_in.msg', + 'DATA'), + ('_tcl_data/msgs/de.msg', '/usr/share/tcltk/tcl8.6/msgs/de.msg', 'DATA'), + ('_tk_data/ttk/button.tcl', '/usr/share/tcltk/tk8.6/ttk/button.tcl', 'DATA'), + ('_tk_data/msgs/eo.msg', '/usr/share/tcltk/tk8.6/msgs/eo.msg', 'DATA'), + ('_tcl_data/msgs/id_id.msg', + '/usr/share/tcltk/tcl8.6/msgs/id_id.msg', + 'DATA'), + ('_tcl_data/msgs/en_sg.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_sg.msg', + 'DATA'), + ('_tcl_data/msgs/es_ve.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ve.msg', + 'DATA'), + ('_tcl_data/msgs/te_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/te_in.msg', + 'DATA'), + ('_tcl_data/encoding/macCyrillic.enc', + '/usr/share/tcltk/tcl8.6/encoding/macCyrillic.enc', + 'DATA'), + ('_tcl_data/encoding/cp950.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp950.enc', + 'DATA'), + ('_tcl_data/encoding/iso8859-11.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-11.enc', + 'DATA'), + ('_tcl_data/msgs/en_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_in.msg', + 'DATA'), + ('_tcl_data/encoding/iso2022-jp.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso2022-jp.enc', + 'DATA'), + ('_tk_data/ttk/utils.tcl', '/usr/share/tcltk/tk8.6/ttk/utils.tcl', 'DATA'), + ('_tcl_data/msgs/te.msg', '/usr/share/tcltk/tcl8.6/msgs/te.msg', 'DATA'), + ('_tcl_data/msgs/ko_kr.msg', + '/usr/share/tcltk/tcl8.6/msgs/ko_kr.msg', + 'DATA'), + ('_tk_data/images/logo.eps', + '/usr/share/tcltk/tk8.6/images/logo.eps', + 'DATA'), + ('_tcl_data/msgs/gv_gb.msg', + '/usr/share/tcltk/tcl8.6/msgs/gv_gb.msg', + 'DATA'), + ('_tk_data/scrlbar.tcl', '/usr/share/tcltk/tk8.6/scrlbar.tcl', 'DATA'), + ('_tcl_data/msgs/en_ca.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_ca.msg', + 'DATA'), + ('_tk_data/images/pwrdLogo200.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo200.gif', + 'DATA'), + ('_tcl_data/msgs/es_do.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_do.msg', + 'DATA'), + ('_tcl_data/msgs/nn.msg', '/usr/share/tcltk/tcl8.6/msgs/nn.msg', 'DATA'), + ('_tk_data/megawidget.tcl', '/usr/share/tcltk/tk8.6/megawidget.tcl', 'DATA'), + ('_tcl_data/encoding/ascii.enc', + '/usr/share/tcltk/tcl8.6/encoding/ascii.enc', + 'DATA'), + ('_tcl_data/encoding/cp775.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp775.enc', + 'DATA'), + ('_tcl_data/msgs/en_nz.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_nz.msg', + 'DATA'), + ('_tcl_data/msgs/eu.msg', '/usr/share/tcltk/tcl8.6/msgs/eu.msg', 'DATA'), + ('_tcl_data/msgs/es_cr.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_cr.msg', + 'DATA'), + ('_tcl_data/msgs/es_sv.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_sv.msg', + 'DATA'), + ('_tcl_data/msgs/af_za.msg', + '/usr/share/tcltk/tcl8.6/msgs/af_za.msg', + 'DATA'), + ('_tcl_data/msgs/es_pa.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_pa.msg', + 'DATA'), + ('_tk_data/ttk/fonts.tcl', '/usr/share/tcltk/tk8.6/ttk/fonts.tcl', 'DATA'), + ('_tk_data/images/pwrdLogo100.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo100.gif', + 'DATA'), + ('_tcl_data/msgs/zh_hk.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_hk.msg', + 'DATA'), + ('_tk_data/mkpsenc.tcl', '/usr/share/tcltk/tk8.6/mkpsenc.tcl', 'DATA'), + ('_tcl_data/msgs/ko.msg', '/usr/share/tcltk/tcl8.6/msgs/ko.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-5.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-5.enc', + 'DATA'), + ('base_library.zip', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/base_library.zip', + 'DATA')], + [], + False, + False, + 1768557080, + [('run', + '/home/ubuntu/.local/lib/python3.11/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run', + 'EXECUTABLE')], + '/opt/computersetup/.pyenv/versions/3.11.6/lib/libpython3.11.so.1.0') diff --git a/build/pointcab_renamer/PKG-00.toc b/build/pointcab_renamer/PKG-00.toc new file mode 100644 index 0000000..a89e9f1 --- /dev/null +++ b/build/pointcab_renamer/PKG-00.toc @@ -0,0 +1,908 @@ +('/home/ubuntu/pointcab_renamer/build/pointcab_renamer/pointcab_renamer.pkg', + {'BINARY': True, + 'DATA': True, + 'EXECUTABLE': True, + 'EXTENSION': True, + 'PYMODULE': True, + 'PYSOURCE': True, + 'PYZ': False, + 'SPLASH': True, + 'SYMLINK': False}, + [('pyi-contents-directory _internal', '', 'OPTION'), + ('PYZ-00.pyz', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/PYZ-00.pyz', + 'PYZ'), + ('python3.11/lib-dynload/_struct.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_struct.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/zlib.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/zlib.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('struct', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/localpycs/struct.pyc', + 'PYMODULE'), + ('pyimod01_archive', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/localpycs/pyimod01_archive.pyc', + 'PYMODULE'), + ('pyimod02_importers', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/localpycs/pyimod02_importers.pyc', + 'PYMODULE'), + ('pyimod03_ctypes', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/localpycs/pyimod03_ctypes.pyc', + 'PYMODULE'), + ('pyiboot01_bootstrap', + '/home/ubuntu/.local/lib/python3.11/site-packages/PyInstaller/loader/pyiboot01_bootstrap.py', + 'PYSOURCE'), + ('pyi_rth_inspect', + '/home/ubuntu/.local/lib/python3.11/site-packages/PyInstaller/hooks/rthooks/pyi_rth_inspect.py', + 'PYSOURCE'), + ('pyi_rth__tkinter', + '/home/ubuntu/.local/lib/python3.11/site-packages/PyInstaller/hooks/rthooks/pyi_rth__tkinter.py', + 'PYSOURCE'), + ('pointcab_renamer', + '/home/ubuntu/pointcab_renamer/pointcab_renamer.py', + 'PYSOURCE'), + ('libpython3.11.so.1.0', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/libpython3.11.so.1.0', + 'BINARY'), + ('python3.11/lib-dynload/_typing.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_typing.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_statistics.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_statistics.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_contextvars.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_contextvars.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_decimal.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_decimal.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_hashlib.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_hashlib.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha3.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha3.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_blake2.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_blake2.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha256.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha256.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_md5.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_md5.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha1.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha1.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_sha512.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha512.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_random.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_random.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_bisect.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_bisect.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/math.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/math.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/unicodedata.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/unicodedata.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/array.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/array.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/select.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/select.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_socket.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_socket.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_csv.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_csv.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/resource.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/resource.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_lzma.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_lzma.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_bz2.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_bz2.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/binascii.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/binascii.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_opcode.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_opcode.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_pickle.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_pickle.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_multibytecodec.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_multibytecodec.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_jp.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_jp.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_kr.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_kr.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_iso2022.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_iso2022.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_cn.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_cn.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_tw.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_tw.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_codecs_hk.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_hk.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_heapq.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_heapq.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_uuid.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_uuid.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/grp.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/grp.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_posixsubprocess.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_posixsubprocess.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/fcntl.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/fcntl.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_elementtree.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_elementtree.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/pyexpat.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/pyexpat.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/termios.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/termios.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_ssl.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_ssl.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_datetime.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_datetime.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('python3.11/lib-dynload/_tkinter.cpython-311-x86_64-linux-gnu.so', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_tkinter.cpython-311-x86_64-linux-gnu.so', + 'EXTENSION'), + ('libcrypto.so.3', '/lib/x86_64-linux-gnu/libcrypto.so.3', 'BINARY'), + ('liblzma.so.5', '/lib/x86_64-linux-gnu/liblzma.so.5', 'BINARY'), + ('libbz2.so.1.0', '/lib/x86_64-linux-gnu/libbz2.so.1.0', 'BINARY'), + ('libz.so.1', '/lib/x86_64-linux-gnu/libz.so.1', 'BINARY'), + ('libuuid.so.1', '/lib/x86_64-linux-gnu/libuuid.so.1', 'BINARY'), + ('libssl.so.3', '/lib/x86_64-linux-gnu/libssl.so.3', 'BINARY'), + ('libfreetype.so.6', '/lib/x86_64-linux-gnu/libfreetype.so.6', 'BINARY'), + ('libXau.so.6', '/lib/x86_64-linux-gnu/libXau.so.6', 'BINARY'), + ('libbrotlicommon.so.1', + '/lib/x86_64-linux-gnu/libbrotlicommon.so.1', + 'BINARY'), + ('libtk8.6.so', '/lib/x86_64-linux-gnu/libtk8.6.so', 'BINARY'), + ('libtcl8.6.so', '/lib/x86_64-linux-gnu/libtcl8.6.so', 'BINARY'), + ('libmd.so.0', '/lib/x86_64-linux-gnu/libmd.so.0', 'BINARY'), + ('libexpat.so.1', '/lib/x86_64-linux-gnu/libexpat.so.1', 'BINARY'), + ('libXext.so.6', '/lib/x86_64-linux-gnu/libXext.so.6', 'BINARY'), + ('libXdmcp.so.6', '/lib/x86_64-linux-gnu/libXdmcp.so.6', 'BINARY'), + ('libbrotlidec.so.1', '/lib/x86_64-linux-gnu/libbrotlidec.so.1', 'BINARY'), + ('libpng16.so.16', '/lib/x86_64-linux-gnu/libpng16.so.16', 'BINARY'), + ('libfontconfig.so.1', '/lib/x86_64-linux-gnu/libfontconfig.so.1', 'BINARY'), + ('libXrender.so.1', '/lib/x86_64-linux-gnu/libXrender.so.1', 'BINARY'), + ('libXft.so.2', '/lib/x86_64-linux-gnu/libXft.so.2', 'BINARY'), + ('libX11.so.6', '/lib/x86_64-linux-gnu/libX11.so.6', 'BINARY'), + ('libbsd.so.0', '/lib/x86_64-linux-gnu/libbsd.so.0', 'BINARY'), + ('libXss.so.1', '/lib/x86_64-linux-gnu/libXss.so.1', 'BINARY'), + ('cluster_cleanup.txt', + '/home/ubuntu/pointcab_renamer/cluster_cleanup.txt', + 'DATA'), + ('_tcl_data/tcl8/tcltest-2.5.5.tm', + '/usr/share/tcltk/tcl8.6/tcl8/tcltest-2.5.5.tm', + 'DATA'), + ('_tk_data/choosedir.tcl', '/usr/share/tcltk/tk8.6/choosedir.tcl', 'DATA'), + ('_tcl_data/msgs/fr_ca.msg', + '/usr/share/tcltk/tcl8.6/msgs/fr_ca.msg', + 'DATA'), + ('_tcl_data/msgs/fi.msg', '/usr/share/tcltk/tcl8.6/msgs/fi.msg', 'DATA'), + ('_tcl_data/encoding/cp874.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp874.enc', + 'DATA'), + ('_tk_data/ttk/ttk.tcl', '/usr/share/tcltk/tk8.6/ttk/ttk.tcl', 'DATA'), + ('_tcl_data/msgs/bn_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/bn_in.msg', + 'DATA'), + ('_tcl_data/encoding/cp850.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp850.enc', + 'DATA'), + ('_tcl_data/msgs/hu.msg', '/usr/share/tcltk/tcl8.6/msgs/hu.msg', 'DATA'), + ('_tk_data/dialog.tcl', '/usr/share/tcltk/tk8.6/dialog.tcl', 'DATA'), + ('_tk_data/ttk/sizegrip.tcl', + '/usr/share/tcltk/tk8.6/ttk/sizegrip.tcl', + 'DATA'), + ('_tcl_data/msgs/mk.msg', '/usr/share/tcltk/tcl8.6/msgs/mk.msg', 'DATA'), + ('_tk_data/images/logoMed.gif', + '/usr/share/tcltk/tk8.6/images/logoMed.gif', + 'DATA'), + ('_tcl_data/msgs/sq.msg', '/usr/share/tcltk/tcl8.6/msgs/sq.msg', 'DATA'), + ('_tk_data/ttk/vistaTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/vistaTheme.tcl', + 'DATA'), + ('_tk_data/msgs/pl.msg', '/usr/share/tcltk/tk8.6/msgs/pl.msg', 'DATA'), + ('_tk_data/ttk/scrollbar.tcl', + '/usr/share/tcltk/tk8.6/ttk/scrollbar.tcl', + 'DATA'), + ('_tk_data/panedwindow.tcl', + '/usr/share/tcltk/tk8.6/panedwindow.tcl', + 'DATA'), + ('_tcl_data/encoding/iso2022.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso2022.enc', + 'DATA'), + ('_tcl_data/encoding/euc-jp.enc', + '/usr/share/tcltk/tcl8.6/encoding/euc-jp.enc', + 'DATA'), + ('_tcl_data/tcl8/msgcat-1.6.1.tm', + '/usr/share/tcltk/tcl8.6/tcl8/msgcat-1.6.1.tm', + 'DATA'), + ('_tcl_data/encoding/dingbats.enc', + '/usr/share/tcltk/tcl8.6/encoding/dingbats.enc', + 'DATA'), + ('_tcl_data/encoding/macRoman.enc', + '/usr/share/tcltk/tcl8.6/encoding/macRoman.enc', + 'DATA'), + ('_tk_data/tkAppInit.c', '/usr/share/tcltk/tk8.6/tkAppInit.c', 'DATA'), + ('_tk_data/ttk/combobox.tcl', + '/usr/share/tcltk/tk8.6/ttk/combobox.tcl', + 'DATA'), + ('_tcl_data/msgs/fr.msg', '/usr/share/tcltk/tcl8.6/msgs/fr.msg', 'DATA'), + ('_tcl_data/tclIndex', '/usr/share/tcltk/tcl8.6/tclIndex', 'DATA'), + ('_tk_data/msgs/en.msg', '/usr/share/tcltk/tk8.6/msgs/en.msg', 'DATA'), + ('_tcl_data/msgs/en_ph.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_ph.msg', + 'DATA'), + ('_tk_data/images/pwrdLogo75.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo75.gif', + 'DATA'), + ('_tcl_data/msgs/zh_cn.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_cn.msg', + 'DATA'), + ('_tcl_data/encoding/cp866.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp866.enc', + 'DATA'), + ('_tcl_data/msgs/mr.msg', '/usr/share/tcltk/tcl8.6/msgs/mr.msg', 'DATA'), + ('_tk_data/ttk/winTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/winTheme.tcl', + 'DATA'), + ('_tcl_data/msgs/es_hn.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_hn.msg', + 'DATA'), + ('_tcl_data/msgs/th.msg', '/usr/share/tcltk/tcl8.6/msgs/th.msg', 'DATA'), + ('_tk_data/msgs/hu.msg', '/usr/share/tcltk/tk8.6/msgs/hu.msg', 'DATA'), + ('_tk_data/images/pwrdLogo175.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo175.gif', + 'DATA'), + ('_tcl_data/encoding/iso8859-9.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-9.enc', + 'DATA'), + ('_tk_data/tkfbox.tcl', '/usr/share/tcltk/tk8.6/tkfbox.tcl', 'DATA'), + ('_tcl_data/msgs/sk.msg', '/usr/share/tcltk/tcl8.6/msgs/sk.msg', 'DATA'), + ('_tcl_data/encoding/macUkraine.enc', + '/usr/share/tcltk/tcl8.6/encoding/macUkraine.enc', + 'DATA'), + ('_tcl_data/encoding/ebcdic.enc', + '/usr/share/tcltk/tcl8.6/encoding/ebcdic.enc', + 'DATA'), + ('_tk_data/ttk/clamTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/clamTheme.tcl', + 'DATA'), + ('_tcl_data/encoding/macJapan.enc', + '/usr/share/tcltk/tcl8.6/encoding/macJapan.enc', + 'DATA'), + ('_tk_data/msgs/fi.msg', '/usr/share/tcltk/tk8.6/msgs/fi.msg', 'DATA'), + ('_tcl_data/tm.tcl', '/usr/share/tcltk/tcl8.6/tm.tcl', 'DATA'), + ('_tk_data/images/logoLarge.gif', + '/usr/share/tcltk/tk8.6/images/logoLarge.gif', + 'DATA'), + ('_tcl_data/msgs/ja.msg', '/usr/share/tcltk/tcl8.6/msgs/ja.msg', 'DATA'), + ('_tcl_data/msgs/gl.msg', '/usr/share/tcltk/tcl8.6/msgs/gl.msg', 'DATA'), + ('_tk_data/safetk.tcl', '/usr/share/tcltk/tk8.6/safetk.tcl', 'DATA'), + ('_tcl_data/msgs/kok.msg', '/usr/share/tcltk/tcl8.6/msgs/kok.msg', 'DATA'), + ('_tcl_data/msgs/mr_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/mr_in.msg', + 'DATA'), + ('_tk_data/iconlist.tcl', '/usr/share/tcltk/tk8.6/iconlist.tcl', 'DATA'), + ('_tcl_data/msgs/es_ni.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ni.msg', + 'DATA'), + ('_tcl_data/encoding/macRomania.enc', + '/usr/share/tcltk/tcl8.6/encoding/macRomania.enc', + 'DATA'), + ('_tk_data/fontchooser.tcl', + '/usr/share/tcltk/tk8.6/fontchooser.tcl', + 'DATA'), + ('_tk_data/ttk/entry.tcl', '/usr/share/tcltk/tk8.6/ttk/entry.tcl', 'DATA'), + ('_tcl_data/encoding/cp737.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp737.enc', + 'DATA'), + ('_tcl_data/msgs/zh_tw.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_tw.msg', + 'DATA'), + ('_tcl_data/msgs/es_co.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_co.msg', + 'DATA'), + ('_tcl_data/msgs/pl.msg', '/usr/share/tcltk/tcl8.6/msgs/pl.msg', 'DATA'), + ('_tk_data/images/logo64.gif', + '/usr/share/tcltk/tk8.6/images/logo64.gif', + 'DATA'), + ('_tcl_data/msgs/sl.msg', '/usr/share/tcltk/tcl8.6/msgs/sl.msg', 'DATA'), + ('_tk_data/msgs/en_gb.msg', '/usr/share/tcltk/tk8.6/msgs/en_gb.msg', 'DATA'), + ('_tcl_data/msgs/kl.msg', '/usr/share/tcltk/tcl8.6/msgs/kl.msg', 'DATA'), + ('_tcl_data/encoding/cp864.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp864.enc', + 'DATA'), + ('_tcl_data/msgs/lv.msg', '/usr/share/tcltk/tcl8.6/msgs/lv.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-8.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-8.enc', + 'DATA'), + ('_tcl_data/msgs/de_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/de_be.msg', + 'DATA'), + ('_tcl_data/encoding/cp936.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp936.enc', + 'DATA'), + ('_tcl_data/msgs/hr.msg', '/usr/share/tcltk/tcl8.6/msgs/hr.msg', 'DATA'), + ('_tcl_data/encoding/big5.enc', + '/usr/share/tcltk/tcl8.6/encoding/big5.enc', + 'DATA'), + ('_tcl_data/encoding/macIceland.enc', + '/usr/share/tcltk/tcl8.6/encoding/macIceland.enc', + 'DATA'), + ('_tcl_data/history.tcl', '/usr/share/tcltk/tcl8.6/history.tcl', 'DATA'), + ('_tcl_data/msgs/hi.msg', '/usr/share/tcltk/tcl8.6/msgs/hi.msg', 'DATA'), + ('_tcl_data/msgs/kw.msg', '/usr/share/tcltk/tcl8.6/msgs/kw.msg', 'DATA'), + ('_tcl_data/msgs/en_au.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_au.msg', + 'DATA'), + ('_tk_data/ttk/scale.tcl', '/usr/share/tcltk/tk8.6/ttk/scale.tcl', 'DATA'), + ('_tcl_data/msgs/fa_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/fa_in.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-4.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-4.enc', + 'DATA'), + ('_tcl_data/msgs/ru_ua.msg', + '/usr/share/tcltk/tcl8.6/msgs/ru_ua.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-6.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-6.enc', + 'DATA'), + ('_tcl_data/encoding/cp1251.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1251.enc', + 'DATA'), + ('_tcl_data/msgs/es_pe.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_pe.msg', + 'DATA'), + ('_tcl_data/opt0.4/pkgIndex.tcl', + '/usr/share/tcltk/tcl8.6/opt0.4/pkgIndex.tcl', + 'DATA'), + ('_tcl_data/msgs/et.msg', '/usr/share/tcltk/tcl8.6/msgs/et.msg', 'DATA'), + ('_tcl_data/msgs/kl_gl.msg', + '/usr/share/tcltk/tcl8.6/msgs/kl_gl.msg', + 'DATA'), + ('_tcl_data/encoding/cp1256.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1256.enc', + 'DATA'), + ('_tcl_data/encoding/jis0201.enc', + '/usr/share/tcltk/tcl8.6/encoding/jis0201.enc', + 'DATA'), + ('_tk_data/text.tcl', '/usr/share/tcltk/tk8.6/text.tcl', 'DATA'), + ('_tk_data/images/tai-ku.gif', + '/usr/share/tcltk/tk8.6/images/tai-ku.gif', + 'DATA'), + ('_tcl_data/msgs/kok_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/kok_in.msg', + 'DATA'), + ('_tk_data/ttk/xpTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/xpTheme.tcl', + 'DATA'), + ('_tk_data/icons.tcl', '/usr/share/tcltk/tk8.6/icons.tcl', 'DATA'), + ('_tcl_data/msgs/sv.msg', '/usr/share/tcltk/tcl8.6/msgs/sv.msg', 'DATA'), + ('_tk_data/ttk/aquaTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/aquaTheme.tcl', + 'DATA'), + ('_tcl_data/msgs/lt.msg', '/usr/share/tcltk/tcl8.6/msgs/lt.msg', 'DATA'), + ('_tcl_data/msgs/fo_fo.msg', + '/usr/share/tcltk/tcl8.6/msgs/fo_fo.msg', + 'DATA'), + ('_tcl_data/encoding/macCentEuro.enc', + '/usr/share/tcltk/tcl8.6/encoding/macCentEuro.enc', + 'DATA'), + ('_tcl_data/msgs/en_bw.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_bw.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-14.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-14.enc', + 'DATA'), + ('_tk_data/ttk/altTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/altTheme.tcl', + 'DATA'), + ('_tcl_data/encoding/gb2312.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb2312.enc', + 'DATA'), + ('_tcl_data/encoding/cp1252.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1252.enc', + 'DATA'), + ('_tcl_data/encoding/cp1255.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1255.enc', + 'DATA'), + ('_tk_data/palette.tcl', '/usr/share/tcltk/tk8.6/palette.tcl', 'DATA'), + ('_tcl_data/msgs/eo.msg', '/usr/share/tcltk/tcl8.6/msgs/eo.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-13.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-13.enc', + 'DATA'), + ('_tcl_data/msgs/nl.msg', '/usr/share/tcltk/tcl8.6/msgs/nl.msg', 'DATA'), + ('_tcl_data/msgs/en_ie.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_ie.msg', + 'DATA'), + ('_tcl_data/msgs/es_bo.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_bo.msg', + 'DATA'), + ('_tcl_data/msgs/ta.msg', '/usr/share/tcltk/tcl8.6/msgs/ta.msg', 'DATA'), + ('_tcl_data/encoding/euc-kr.enc', + '/usr/share/tcltk/tcl8.6/encoding/euc-kr.enc', + 'DATA'), + ('_tk_data/scale.tcl', '/usr/share/tcltk/tk8.6/scale.tcl', 'DATA'), + ('_tcl_data/tclAppInit.c', '/usr/share/tcltk/tcl8.6/tclAppInit.c', 'DATA'), + ('_tcl_data/encoding/iso2022-kr.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso2022-kr.enc', + 'DATA'), + ('_tcl_data/msgs/ar_jo.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_jo.msg', + 'DATA'), + ('_tcl_data/msgs/en_gb.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_gb.msg', + 'DATA'), + ('_tcl_data/msgs/sh.msg', '/usr/share/tcltk/tcl8.6/msgs/sh.msg', 'DATA'), + ('_tcl_data/msgs/da.msg', '/usr/share/tcltk/tcl8.6/msgs/da.msg', 'DATA'), + ('_tcl_data/msgs/nl_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/nl_be.msg', + 'DATA'), + ('_tcl_data/encoding/cp852.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp852.enc', + 'DATA'), + ('_tcl_data/msgs/be.msg', '/usr/share/tcltk/tcl8.6/msgs/be.msg', 'DATA'), + ('_tk_data/msgs/nl.msg', '/usr/share/tcltk/tk8.6/msgs/nl.msg', 'DATA'), + ('_tcl_data/msgs/fa_ir.msg', + '/usr/share/tcltk/tcl8.6/msgs/fa_ir.msg', + 'DATA'), + ('_tcl_data/msgs/it.msg', '/usr/share/tcltk/tcl8.6/msgs/it.msg', 'DATA'), + ('_tk_data/ttk/cursors.tcl', + '/usr/share/tcltk/tk8.6/ttk/cursors.tcl', + 'DATA'), + ('_tcl_data/msgs/zh_sg.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_sg.msg', + 'DATA'), + ('_tk_data/spinbox.tcl', '/usr/share/tcltk/tk8.6/spinbox.tcl', 'DATA'), + ('_tk_data/msgs/es.msg', '/usr/share/tcltk/tk8.6/msgs/es.msg', 'DATA'), + ('_tk_data/msgs/el.msg', '/usr/share/tcltk/tk8.6/msgs/el.msg', 'DATA'), + ('_tcl_data/msgs/ar_sy.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_sy.msg', + 'DATA'), + ('_tcl_data/msgs/ar.msg', '/usr/share/tcltk/tcl8.6/msgs/ar.msg', 'DATA'), + ('_tcl_data/msgs/ga_ie.msg', + '/usr/share/tcltk/tcl8.6/msgs/ga_ie.msg', + 'DATA'), + ('_tk_data/msgs/sv.msg', '/usr/share/tcltk/tk8.6/msgs/sv.msg', 'DATA'), + ('_tk_data/tclIndex', '/usr/share/tcltk/tk8.6/tclIndex', 'DATA'), + ('_tcl_data/msgs/uk.msg', '/usr/share/tcltk/tcl8.6/msgs/uk.msg', 'DATA'), + ('_tcl_data/word.tcl', '/usr/share/tcltk/tcl8.6/word.tcl', 'DATA'), + ('_tcl_data/msgs/ga.msg', '/usr/share/tcltk/tcl8.6/msgs/ga.msg', 'DATA'), + ('_tcl_data/msgs/en_za.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_za.msg', + 'DATA'), + ('_tcl_data/msgs/fr_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/fr_be.msg', + 'DATA'), + ('_tk_data/msgbox.tcl', '/usr/share/tcltk/tk8.6/msgbox.tcl', 'DATA'), + ('_tcl_data/encoding/cp1257.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1257.enc', + 'DATA'), + ('_tcl_data/encoding/iso8859-3.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-3.enc', + 'DATA'), + ('_tcl_data/encoding/euc-cn.enc', + '/usr/share/tcltk/tcl8.6/encoding/euc-cn.enc', + 'DATA'), + ('_tcl_data/msgs/id.msg', '/usr/share/tcltk/tcl8.6/msgs/id.msg', 'DATA'), + ('_tk_data/ttk/menubutton.tcl', + '/usr/share/tcltk/tk8.6/ttk/menubutton.tcl', + 'DATA'), + ('_tk_data/tearoff.tcl', '/usr/share/tcltk/tk8.6/tearoff.tcl', 'DATA'), + ('_tk_data/optMenu.tcl', '/usr/share/tcltk/tk8.6/optMenu.tcl', 'DATA'), + ('_tcl_data/tcl8/platform/shell-1.1.4.tm', + '/usr/share/tcltk/tcl8.6/tcl8/platform/shell-1.1.4.tm', + 'DATA'), + ('_tcl_data/encoding/cp860.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp860.enc', + 'DATA'), + ('_tcl_data/encoding/macCroatian.enc', + '/usr/share/tcltk/tcl8.6/encoding/macCroatian.enc', + 'DATA'), + ('_tcl_data/encoding/cp1253.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1253.enc', + 'DATA'), + ('_tcl_data/msgs/gv.msg', '/usr/share/tcltk/tcl8.6/msgs/gv.msg', 'DATA'), + ('_tcl_data/init.tcl', '/usr/share/tcltk/tcl8.6/init.tcl', 'DATA'), + ('_tcl_data/msgs/fr_ch.msg', + '/usr/share/tcltk/tcl8.6/msgs/fr_ch.msg', + 'DATA'), + ('_tcl_data/encoding/iso8859-10.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-10.enc', + 'DATA'), + ('_tcl_data/msgs/bg.msg', '/usr/share/tcltk/tcl8.6/msgs/bg.msg', 'DATA'), + ('_tcl_data/msgs/ro.msg', '/usr/share/tcltk/tcl8.6/msgs/ro.msg', 'DATA'), + ('_tcl_data/encoding/cp857.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp857.enc', + 'DATA'), + ('_tcl_data/msgs/gl_es.msg', + '/usr/share/tcltk/tcl8.6/msgs/gl_es.msg', + 'DATA'), + ('_tcl_data/msgs/es_uy.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_uy.msg', + 'DATA'), + ('_tk_data/ttk/panedwindow.tcl', + '/usr/share/tcltk/tk8.6/ttk/panedwindow.tcl', + 'DATA'), + ('_tcl_data/msgs/af.msg', '/usr/share/tcltk/tcl8.6/msgs/af.msg', 'DATA'), + ('_tcl_data/msgs/de_at.msg', + '/usr/share/tcltk/tcl8.6/msgs/de_at.msg', + 'DATA'), + ('_tcl_data/auto.tcl', '/usr/share/tcltk/tcl8.6/auto.tcl', 'DATA'), + ('_tcl_data/msgs/ms_my.msg', + '/usr/share/tcltk/tcl8.6/msgs/ms_my.msg', + 'DATA'), + ('_tcl_data/msgs/ms.msg', '/usr/share/tcltk/tcl8.6/msgs/ms.msg', 'DATA'), + ('_tcl_data/encoding/gb12345.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb12345.enc', + 'DATA'), + ('_tcl_data/msgs/ru.msg', '/usr/share/tcltk/tcl8.6/msgs/ru.msg', 'DATA'), + ('_tcl_data/msgs/sr.msg', '/usr/share/tcltk/tcl8.6/msgs/sr.msg', 'DATA'), + ('_tcl_data/msgs/zh.msg', '/usr/share/tcltk/tcl8.6/msgs/zh.msg', 'DATA'), + ('_tcl_data/msgs/mt.msg', '/usr/share/tcltk/tcl8.6/msgs/mt.msg', 'DATA'), + ('_tcl_data/encoding/jis0208.enc', + '/usr/share/tcltk/tcl8.6/encoding/jis0208.enc', + 'DATA'), + ('_tcl_data/encoding/iso8859-15.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-15.enc', + 'DATA'), + ('_tk_data/msgs/zh_cn.msg', '/usr/share/tcltk/tk8.6/msgs/zh_cn.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-16.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-16.enc', + 'DATA'), + ('_tcl_data/encoding/koi8-u.enc', + '/usr/share/tcltk/tcl8.6/encoding/koi8-u.enc', + 'DATA'), + ('_tk_data/unsupported.tcl', + '/usr/share/tcltk/tk8.6/unsupported.tcl', + 'DATA'), + ('_tcl_data/encoding/cp865.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp865.enc', + 'DATA'), + ('_tcl_data/msgs/es_ar.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ar.msg', + 'DATA'), + ('_tk_data/msgs/cs.msg', '/usr/share/tcltk/tk8.6/msgs/cs.msg', 'DATA'), + ('_tcl_data/encoding/koi8-r.enc', + '/usr/share/tcltk/tcl8.6/encoding/koi8-r.enc', + 'DATA'), + ('_tcl_data/parray.tcl', '/usr/share/tcltk/tcl8.6/parray.tcl', 'DATA'), + ('_tk_data/ttk/classicTheme.tcl', + '/usr/share/tcltk/tk8.6/ttk/classicTheme.tcl', + 'DATA'), + ('_tcl_data/encoding/cp1254.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1254.enc', + 'DATA'), + ('_tcl_data/encoding/symbol.enc', + '/usr/share/tcltk/tcl8.6/encoding/symbol.enc', + 'DATA'), + ('_tcl_data/msgs/sw.msg', '/usr/share/tcltk/tcl8.6/msgs/sw.msg', 'DATA'), + ('_tk_data/images/pwrdLogo.eps', + '/usr/share/tcltk/tk8.6/images/pwrdLogo.eps', + 'DATA'), + ('_tcl_data/msgs/es.msg', '/usr/share/tcltk/tcl8.6/msgs/es.msg', 'DATA'), + ('_tcl_data/package.tcl', '/usr/share/tcltk/tcl8.6/package.tcl', 'DATA'), + ('_tcl_data/encoding/macTurkish.enc', + '/usr/share/tcltk/tcl8.6/encoding/macTurkish.enc', + 'DATA'), + ('_tk_data/bgerror.tcl', '/usr/share/tcltk/tk8.6/bgerror.tcl', 'DATA'), + ('_tk_data/listbox.tcl', '/usr/share/tcltk/tk8.6/listbox.tcl', 'DATA'), + ('_tcl_data/msgs/es_mx.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_mx.msg', + 'DATA'), + ('_tk_data/msgs/it.msg', '/usr/share/tcltk/tk8.6/msgs/it.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-2.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-2.enc', + 'DATA'), + ('_tcl_data/encoding/cp1250.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1250.enc', + 'DATA'), + ('_tcl_data/encoding/macThai.enc', + '/usr/share/tcltk/tcl8.6/encoding/macThai.enc', + 'DATA'), + ('_tcl_data/encoding/macGreek.enc', + '/usr/share/tcltk/tcl8.6/encoding/macGreek.enc', + 'DATA'), + ('_tcl_data/opt0.4/optparse.tcl', + '/usr/share/tcltk/tcl8.6/opt0.4/optparse.tcl', + 'DATA'), + ('_tk_data/msgs/ru.msg', '/usr/share/tcltk/tk8.6/msgs/ru.msg', 'DATA'), + ('_tcl_data/encoding/ksc5601.enc', + '/usr/share/tcltk/tcl8.6/encoding/ksc5601.enc', + 'DATA'), + ('_tcl_data/encoding/jis0212.enc', + '/usr/share/tcltk/tcl8.6/encoding/jis0212.enc', + 'DATA'), + ('_tcl_data/encoding/tis-620.enc', + '/usr/share/tcltk/tcl8.6/encoding/tis-620.enc', + 'DATA'), + ('_tk_data/tk.tcl', '/usr/share/tcltk/tk8.6/tk.tcl', 'DATA'), + ('_tk_data/console.tcl', '/usr/share/tcltk/tk8.6/console.tcl', 'DATA'), + ('_tcl_data/msgs/it_ch.msg', + '/usr/share/tcltk/tcl8.6/msgs/it_ch.msg', + 'DATA'), + ('_tcl_data/encoding/macDingbats.enc', + '/usr/share/tcltk/tcl8.6/encoding/macDingbats.enc', + 'DATA'), + ('_tcl_data/encoding/cp437.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp437.enc', + 'DATA'), + ('_tcl_data/msgs/en_be.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_be.msg', + 'DATA'), + ('_tcl_data/tcl8/platform-1.0.19.tm', + '/usr/share/tcltk/tcl8.6/tcl8/platform-1.0.19.tm', + 'DATA'), + ('_tcl_data/msgs/pt_br.msg', + '/usr/share/tcltk/tcl8.6/msgs/pt_br.msg', + 'DATA'), + ('_tcl_data/clock.tcl', '/usr/share/tcltk/tcl8.6/clock.tcl', 'DATA'), + ('_tk_data/ttk/spinbox.tcl', + '/usr/share/tcltk/tk8.6/ttk/spinbox.tcl', + 'DATA'), + ('_tcl_data/encoding/iso8859-7.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-7.enc', + 'DATA'), + ('_tk_data/obsolete.tcl', '/usr/share/tcltk/tk8.6/obsolete.tcl', 'DATA'), + ('_tcl_data/tcl8/http-2.9.8.tm', + '/usr/share/tcltk/tcl8.6/tcl8/http-2.9.8.tm', + 'DATA'), + ('_tcl_data/msgs/es_ec.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ec.msg', + 'DATA'), + ('_tk_data/msgs/pt.msg', '/usr/share/tcltk/tk8.6/msgs/pt.msg', 'DATA'), + ('_tcl_data/encoding/cns11643.enc', + '/usr/share/tcltk/tcl8.6/encoding/cns11643.enc', + 'DATA'), + ('_tk_data/focus.tcl', '/usr/share/tcltk/tk8.6/focus.tcl', 'DATA'), + ('_tcl_data/msgs/is.msg', '/usr/share/tcltk/tcl8.6/msgs/is.msg', 'DATA'), + ('_tcl_data/msgs/en_hk.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_hk.msg', + 'DATA'), + ('_tcl_data/msgs/bn.msg', '/usr/share/tcltk/tcl8.6/msgs/bn.msg', 'DATA'), + ('_tcl_data/encoding/cp861.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp861.enc', + 'DATA'), + ('_tcl_data/msgs/kw_gb.msg', + '/usr/share/tcltk/tcl8.6/msgs/kw_gb.msg', + 'DATA'), + ('_tcl_data/encoding/cp869.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp869.enc', + 'DATA'), + ('_tcl_data/msgs/en_zw.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_zw.msg', + 'DATA'), + ('_tk_data/button.tcl', '/usr/share/tcltk/tk8.6/button.tcl', 'DATA'), + ('_tk_data/ttk/treeview.tcl', + '/usr/share/tcltk/tk8.6/ttk/treeview.tcl', + 'DATA'), + ('_tcl_data/msgs/es_cl.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_cl.msg', + 'DATA'), + ('_tk_data/ttk/defaults.tcl', + '/usr/share/tcltk/tk8.6/ttk/defaults.tcl', + 'DATA'), + ('_tcl_data/encoding/cp932.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp932.enc', + 'DATA'), + ('_tk_data/xmfbox.tcl', '/usr/share/tcltk/tk8.6/xmfbox.tcl', 'DATA'), + ('_tcl_data/msgs/el.msg', '/usr/share/tcltk/tcl8.6/msgs/el.msg', 'DATA'), + ('_tcl_data/msgs/fo.msg', '/usr/share/tcltk/tcl8.6/msgs/fo.msg', 'DATA'), + ('_tcl_data/encoding/cp949.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp949.enc', + 'DATA'), + ('_tcl_data/safe.tcl', '/usr/share/tcltk/tcl8.6/safe.tcl', 'DATA'), + ('_tk_data/images/pwrdLogo150.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo150.gif', + 'DATA'), + ('_tk_data/entry.tcl', '/usr/share/tcltk/tk8.6/entry.tcl', 'DATA'), + ('_tcl_data/msgs/vi.msg', '/usr/share/tcltk/tcl8.6/msgs/vi.msg', 'DATA'), + ('_tcl_data/http1.0/pkgIndex.tcl', + '/usr/share/tcltk/tcl8.6/http1.0/pkgIndex.tcl', + 'DATA'), + ('_tk_data/msgs/de.msg', '/usr/share/tcltk/tk8.6/msgs/de.msg', 'DATA'), + ('_tcl_data/encoding/cp855.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp855.enc', + 'DATA'), + ('_tcl_data/msgs/ar_lb.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_lb.msg', + 'DATA'), + ('_tcl_data/msgs/eu_es.msg', + '/usr/share/tcltk/tcl8.6/msgs/eu_es.msg', + 'DATA'), + ('_tcl_data/msgs/cs.msg', '/usr/share/tcltk/tcl8.6/msgs/cs.msg', 'DATA'), + ('_tcl_data/encoding/cp862.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp862.enc', + 'DATA'), + ('_tcl_data/msgs/pt.msg', '/usr/share/tcltk/tcl8.6/msgs/pt.msg', 'DATA'), + ('_tk_data/ttk/progress.tcl', + '/usr/share/tcltk/tk8.6/ttk/progress.tcl', + 'DATA'), + ('_tcl_data/encoding/gb2312-raw.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb2312-raw.enc', + 'DATA'), + ('_tk_data/clrpick.tcl', '/usr/share/tcltk/tk8.6/clrpick.tcl', 'DATA'), + ('_tcl_data/encoding/shiftjis.enc', + '/usr/share/tcltk/tcl8.6/encoding/shiftjis.enc', + 'DATA'), + ('_tcl_data/msgs/nb.msg', '/usr/share/tcltk/tcl8.6/msgs/nb.msg', 'DATA'), + ('_tcl_data/msgs/he.msg', '/usr/share/tcltk/tcl8.6/msgs/he.msg', 'DATA'), + ('_tk_data/msgs/fr.msg', '/usr/share/tcltk/tk8.6/msgs/fr.msg', 'DATA'), + ('_tk_data/comdlg.tcl', '/usr/share/tcltk/tk8.6/comdlg.tcl', 'DATA'), + ('_tcl_data/http1.0/http.tcl', + '/usr/share/tcltk/tcl8.6/http1.0/http.tcl', + 'DATA'), + ('_tcl_data/msgs/fa.msg', '/usr/share/tcltk/tcl8.6/msgs/fa.msg', 'DATA'), + ('_tcl_data/encoding/gb1988.enc', + '/usr/share/tcltk/tcl8.6/encoding/gb1988.enc', + 'DATA'), + ('_tk_data/images/logo100.gif', + '/usr/share/tcltk/tk8.6/images/logo100.gif', + 'DATA'), + ('_tcl_data/msgs/es_gt.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_gt.msg', + 'DATA'), + ('_tcl_data/msgs/ta_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/ta_in.msg', + 'DATA'), + ('_tcl_data/encoding/cp863.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp863.enc', + 'DATA'), + ('_tcl_data/msgs/tr.msg', '/usr/share/tcltk/tcl8.6/msgs/tr.msg', 'DATA'), + ('_tcl_data/msgs/es_pr.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_pr.msg', + 'DATA'), + ('_tcl_data/msgs/ca.msg', '/usr/share/tcltk/tcl8.6/msgs/ca.msg', 'DATA'), + ('_tk_data/menu.tcl', '/usr/share/tcltk/tk8.6/menu.tcl', 'DATA'), + ('_tk_data/msgs/da.msg', '/usr/share/tcltk/tk8.6/msgs/da.msg', 'DATA'), + ('_tk_data/images/README', '/usr/share/tcltk/tk8.6/images/README', 'DATA'), + ('_tcl_data/encoding/iso8859-1.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-1.enc', + 'DATA'), + ('_tcl_data/msgs/es_py.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_py.msg', + 'DATA'), + ('_tk_data/ttk/notebook.tcl', + '/usr/share/tcltk/tk8.6/ttk/notebook.tcl', + 'DATA'), + ('_tcl_data/msgs/ar_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/ar_in.msg', + 'DATA'), + ('_tcl_data/encoding/cp1258.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp1258.enc', + 'DATA'), + ('_tcl_data/msgs/hi_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/hi_in.msg', + 'DATA'), + ('_tcl_data/msgs/de.msg', '/usr/share/tcltk/tcl8.6/msgs/de.msg', 'DATA'), + ('_tk_data/ttk/button.tcl', '/usr/share/tcltk/tk8.6/ttk/button.tcl', 'DATA'), + ('_tk_data/msgs/eo.msg', '/usr/share/tcltk/tk8.6/msgs/eo.msg', 'DATA'), + ('_tcl_data/msgs/id_id.msg', + '/usr/share/tcltk/tcl8.6/msgs/id_id.msg', + 'DATA'), + ('_tcl_data/msgs/en_sg.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_sg.msg', + 'DATA'), + ('_tcl_data/msgs/es_ve.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_ve.msg', + 'DATA'), + ('_tcl_data/msgs/te_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/te_in.msg', + 'DATA'), + ('_tcl_data/encoding/macCyrillic.enc', + '/usr/share/tcltk/tcl8.6/encoding/macCyrillic.enc', + 'DATA'), + ('_tcl_data/encoding/cp950.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp950.enc', + 'DATA'), + ('_tcl_data/encoding/iso8859-11.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-11.enc', + 'DATA'), + ('_tcl_data/msgs/en_in.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_in.msg', + 'DATA'), + ('_tcl_data/encoding/iso2022-jp.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso2022-jp.enc', + 'DATA'), + ('_tk_data/ttk/utils.tcl', '/usr/share/tcltk/tk8.6/ttk/utils.tcl', 'DATA'), + ('_tcl_data/msgs/te.msg', '/usr/share/tcltk/tcl8.6/msgs/te.msg', 'DATA'), + ('_tcl_data/msgs/ko_kr.msg', + '/usr/share/tcltk/tcl8.6/msgs/ko_kr.msg', + 'DATA'), + ('_tk_data/images/logo.eps', + '/usr/share/tcltk/tk8.6/images/logo.eps', + 'DATA'), + ('_tcl_data/msgs/gv_gb.msg', + '/usr/share/tcltk/tcl8.6/msgs/gv_gb.msg', + 'DATA'), + ('_tk_data/scrlbar.tcl', '/usr/share/tcltk/tk8.6/scrlbar.tcl', 'DATA'), + ('_tcl_data/msgs/en_ca.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_ca.msg', + 'DATA'), + ('_tk_data/images/pwrdLogo200.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo200.gif', + 'DATA'), + ('_tcl_data/msgs/es_do.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_do.msg', + 'DATA'), + ('_tcl_data/msgs/nn.msg', '/usr/share/tcltk/tcl8.6/msgs/nn.msg', 'DATA'), + ('_tk_data/megawidget.tcl', '/usr/share/tcltk/tk8.6/megawidget.tcl', 'DATA'), + ('_tcl_data/encoding/ascii.enc', + '/usr/share/tcltk/tcl8.6/encoding/ascii.enc', + 'DATA'), + ('_tcl_data/encoding/cp775.enc', + '/usr/share/tcltk/tcl8.6/encoding/cp775.enc', + 'DATA'), + ('_tcl_data/msgs/en_nz.msg', + '/usr/share/tcltk/tcl8.6/msgs/en_nz.msg', + 'DATA'), + ('_tcl_data/msgs/eu.msg', '/usr/share/tcltk/tcl8.6/msgs/eu.msg', 'DATA'), + ('_tcl_data/msgs/es_cr.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_cr.msg', + 'DATA'), + ('_tcl_data/msgs/es_sv.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_sv.msg', + 'DATA'), + ('_tcl_data/msgs/af_za.msg', + '/usr/share/tcltk/tcl8.6/msgs/af_za.msg', + 'DATA'), + ('_tcl_data/msgs/es_pa.msg', + '/usr/share/tcltk/tcl8.6/msgs/es_pa.msg', + 'DATA'), + ('_tk_data/ttk/fonts.tcl', '/usr/share/tcltk/tk8.6/ttk/fonts.tcl', 'DATA'), + ('_tk_data/images/pwrdLogo100.gif', + '/usr/share/tcltk/tk8.6/images/pwrdLogo100.gif', + 'DATA'), + ('_tcl_data/msgs/zh_hk.msg', + '/usr/share/tcltk/tcl8.6/msgs/zh_hk.msg', + 'DATA'), + ('_tk_data/mkpsenc.tcl', '/usr/share/tcltk/tk8.6/mkpsenc.tcl', 'DATA'), + ('_tcl_data/msgs/ko.msg', '/usr/share/tcltk/tcl8.6/msgs/ko.msg', 'DATA'), + ('_tcl_data/encoding/iso8859-5.enc', + '/usr/share/tcltk/tcl8.6/encoding/iso8859-5.enc', + 'DATA'), + ('base_library.zip', + '/home/ubuntu/pointcab_renamer/build/pointcab_renamer/base_library.zip', + 'DATA')], + 'libpython3.11.so.1.0', + False, + False, + False, + [], + None, + None, + None) diff --git a/build/pointcab_renamer/PYZ-00.pyz b/build/pointcab_renamer/PYZ-00.pyz new file mode 100644 index 0000000..1e045f0 Binary files /dev/null and b/build/pointcab_renamer/PYZ-00.pyz differ diff --git a/build/pointcab_renamer/PYZ-00.toc b/build/pointcab_renamer/PYZ-00.toc new file mode 100644 index 0000000..00660fc --- /dev/null +++ b/build/pointcab_renamer/PYZ-00.toc @@ -0,0 +1,412 @@ +('/home/ubuntu/pointcab_renamer/build/pointcab_renamer/PYZ-00.pyz', + [('_compat_pickle', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_compat_pickle.py', + 'PYMODULE'), + ('_compression', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_compression.py', + 'PYMODULE'), + ('_py_abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_py_abc.py', + 'PYMODULE'), + ('_pydecimal', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_pydecimal.py', + 'PYMODULE'), + ('_strptime', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_strptime.py', + 'PYMODULE'), + ('_threading_local', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/_threading_local.py', + 'PYMODULE'), + ('argparse', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/argparse.py', + 'PYMODULE'), + ('ast', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/ast.py', + 'PYMODULE'), + ('base64', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/base64.py', + 'PYMODULE'), + ('bisect', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/bisect.py', + 'PYMODULE'), + ('bz2', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/bz2.py', + 'PYMODULE'), + ('calendar', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/calendar.py', + 'PYMODULE'), + ('contextlib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/contextlib.py', + 'PYMODULE'), + ('contextvars', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/contextvars.py', + 'PYMODULE'), + ('copy', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/copy.py', + 'PYMODULE'), + ('csv', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/csv.py', + 'PYMODULE'), + ('dataclasses', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/dataclasses.py', + 'PYMODULE'), + ('datetime', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/datetime.py', + 'PYMODULE'), + ('decimal', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/decimal.py', + 'PYMODULE'), + ('dis', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/dis.py', + 'PYMODULE'), + ('email', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/__init__.py', + 'PYMODULE'), + ('email._encoded_words', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/_encoded_words.py', + 'PYMODULE'), + ('email._header_value_parser', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/_header_value_parser.py', + 'PYMODULE'), + ('email._parseaddr', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/_parseaddr.py', + 'PYMODULE'), + ('email._policybase', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/_policybase.py', + 'PYMODULE'), + ('email.base64mime', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/base64mime.py', + 'PYMODULE'), + ('email.charset', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/charset.py', + 'PYMODULE'), + ('email.contentmanager', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/contentmanager.py', + 'PYMODULE'), + ('email.encoders', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/encoders.py', + 'PYMODULE'), + ('email.errors', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/errors.py', + 'PYMODULE'), + ('email.feedparser', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/feedparser.py', + 'PYMODULE'), + ('email.generator', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/generator.py', + 'PYMODULE'), + ('email.header', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/header.py', + 'PYMODULE'), + ('email.headerregistry', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/headerregistry.py', + 'PYMODULE'), + ('email.iterators', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/iterators.py', + 'PYMODULE'), + ('email.message', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/message.py', + 'PYMODULE'), + ('email.parser', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/parser.py', + 'PYMODULE'), + ('email.policy', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/policy.py', + 'PYMODULE'), + ('email.quoprimime', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/quoprimime.py', + 'PYMODULE'), + ('email.utils', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/email/utils.py', + 'PYMODULE'), + ('fnmatch', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/fnmatch.py', + 'PYMODULE'), + ('fractions', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/fractions.py', + 'PYMODULE'), + ('ftplib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/ftplib.py', + 'PYMODULE'), + ('getopt', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/getopt.py', + 'PYMODULE'), + ('getpass', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/getpass.py', + 'PYMODULE'), + ('gettext', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/gettext.py', + 'PYMODULE'), + ('gzip', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/gzip.py', + 'PYMODULE'), + ('hashlib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/hashlib.py', + 'PYMODULE'), + ('http', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/http/__init__.py', + 'PYMODULE'), + ('http.client', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/http/client.py', + 'PYMODULE'), + ('http.cookiejar', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/http/cookiejar.py', + 'PYMODULE'), + ('importlib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/__init__.py', + 'PYMODULE'), + ('importlib._abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/_abc.py', + 'PYMODULE'), + ('importlib._bootstrap', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/_bootstrap.py', + 'PYMODULE'), + ('importlib._bootstrap_external', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/_bootstrap_external.py', + 'PYMODULE'), + ('importlib.abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/abc.py', + 'PYMODULE'), + ('importlib.machinery', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/machinery.py', + 'PYMODULE'), + ('importlib.metadata', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/__init__.py', + 'PYMODULE'), + ('importlib.metadata._adapters', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_adapters.py', + 'PYMODULE'), + ('importlib.metadata._collections', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_collections.py', + 'PYMODULE'), + ('importlib.metadata._functools', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_functools.py', + 'PYMODULE'), + ('importlib.metadata._itertools', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_itertools.py', + 'PYMODULE'), + ('importlib.metadata._meta', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_meta.py', + 'PYMODULE'), + ('importlib.metadata._text', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/metadata/_text.py', + 'PYMODULE'), + ('importlib.readers', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/readers.py', + 'PYMODULE'), + ('importlib.resources', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/__init__.py', + 'PYMODULE'), + ('importlib.resources._adapters', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/_adapters.py', + 'PYMODULE'), + ('importlib.resources._common', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/_common.py', + 'PYMODULE'), + ('importlib.resources._itertools', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/_itertools.py', + 'PYMODULE'), + ('importlib.resources._legacy', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/_legacy.py', + 'PYMODULE'), + ('importlib.resources.abc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/abc.py', + 'PYMODULE'), + ('importlib.resources.readers', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/resources/readers.py', + 'PYMODULE'), + ('importlib.util', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/importlib/util.py', + 'PYMODULE'), + ('inspect', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/inspect.py', + 'PYMODULE'), + ('ipaddress', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/ipaddress.py', + 'PYMODULE'), + ('logging', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/logging/__init__.py', + 'PYMODULE'), + ('lzma', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lzma.py', + 'PYMODULE'), + ('mimetypes', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/mimetypes.py', + 'PYMODULE'), + ('netrc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/netrc.py', + 'PYMODULE'), + ('nturl2path', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/nturl2path.py', + 'PYMODULE'), + ('numbers', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/numbers.py', + 'PYMODULE'), + ('opcode', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/opcode.py', + 'PYMODULE'), + ('pathlib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/pathlib.py', + 'PYMODULE'), + ('pickle', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/pickle.py', + 'PYMODULE'), + ('platform', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/platform.py', + 'PYMODULE'), + ('pprint', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/pprint.py', + 'PYMODULE'), + ('py_compile', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/py_compile.py', + 'PYMODULE'), + ('quopri', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/quopri.py', + 'PYMODULE'), + ('random', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/random.py', + 'PYMODULE'), + ('selectors', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/selectors.py', + 'PYMODULE'), + ('shlex', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/shlex.py', + 'PYMODULE'), + ('shutil', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/shutil.py', + 'PYMODULE'), + ('signal', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/signal.py', + 'PYMODULE'), + ('socket', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/socket.py', + 'PYMODULE'), + ('ssl', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/ssl.py', + 'PYMODULE'), + ('statistics', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/statistics.py', + 'PYMODULE'), + ('string', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/string.py', + 'PYMODULE'), + ('stringprep', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/stringprep.py', + 'PYMODULE'), + ('subprocess', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/subprocess.py', + 'PYMODULE'), + ('tarfile', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tarfile.py', + 'PYMODULE'), + ('tempfile', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tempfile.py', + 'PYMODULE'), + ('textwrap', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/textwrap.py', + 'PYMODULE'), + ('threading', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/threading.py', + 'PYMODULE'), + ('tkinter', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/__init__.py', + 'PYMODULE'), + ('tkinter.commondialog', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/commondialog.py', + 'PYMODULE'), + ('tkinter.constants', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/constants.py', + 'PYMODULE'), + ('tkinter.dialog', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/dialog.py', + 'PYMODULE'), + ('tkinter.filedialog', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/filedialog.py', + 'PYMODULE'), + ('tkinter.messagebox', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/messagebox.py', + 'PYMODULE'), + ('tkinter.scrolledtext', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/scrolledtext.py', + 'PYMODULE'), + ('tkinter.simpledialog', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/simpledialog.py', + 'PYMODULE'), + ('tkinter.ttk', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tkinter/ttk.py', + 'PYMODULE'), + ('token', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/token.py', + 'PYMODULE'), + ('tokenize', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tokenize.py', + 'PYMODULE'), + ('tracemalloc', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/tracemalloc.py', + 'PYMODULE'), + ('typing', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/typing.py', + 'PYMODULE'), + ('urllib', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/__init__.py', + 'PYMODULE'), + ('urllib.error', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/error.py', + 'PYMODULE'), + ('urllib.parse', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/parse.py', + 'PYMODULE'), + ('urllib.request', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/request.py', + 'PYMODULE'), + ('urllib.response', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/urllib/response.py', + 'PYMODULE'), + ('uuid', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/uuid.py', + 'PYMODULE'), + ('xml', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/__init__.py', + 'PYMODULE'), + ('xml.etree', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/__init__.py', + 'PYMODULE'), + ('xml.etree.ElementInclude', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/ElementInclude.py', + 'PYMODULE'), + ('xml.etree.ElementPath', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/ElementPath.py', + 'PYMODULE'), + ('xml.etree.ElementTree', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/ElementTree.py', + 'PYMODULE'), + ('xml.etree.cElementTree', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/etree/cElementTree.py', + 'PYMODULE'), + ('xml.parsers', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/parsers/__init__.py', + 'PYMODULE'), + ('xml.parsers.expat', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/parsers/expat.py', + 'PYMODULE'), + ('xml.sax', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/__init__.py', + 'PYMODULE'), + ('xml.sax._exceptions', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/_exceptions.py', + 'PYMODULE'), + ('xml.sax.expatreader', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/expatreader.py', + 'PYMODULE'), + ('xml.sax.handler', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/handler.py', + 'PYMODULE'), + ('xml.sax.saxutils', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/saxutils.py', + 'PYMODULE'), + ('xml.sax.xmlreader', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/xml/sax/xmlreader.py', + 'PYMODULE'), + ('zipfile', + '/opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/zipfile.py', + 'PYMODULE')]) diff --git a/build/pointcab_renamer/base_library.zip b/build/pointcab_renamer/base_library.zip new file mode 100644 index 0000000..98b9b85 Binary files /dev/null and b/build/pointcab_renamer/base_library.zip differ diff --git a/build/pointcab_renamer/localpycs/pyimod01_archive.pyc b/build/pointcab_renamer/localpycs/pyimod01_archive.pyc new file mode 100644 index 0000000..0f1b13b Binary files /dev/null and b/build/pointcab_renamer/localpycs/pyimod01_archive.pyc differ diff --git a/build/pointcab_renamer/localpycs/pyimod02_importers.pyc b/build/pointcab_renamer/localpycs/pyimod02_importers.pyc new file mode 100644 index 0000000..0b88810 Binary files /dev/null and b/build/pointcab_renamer/localpycs/pyimod02_importers.pyc differ diff --git a/build/pointcab_renamer/localpycs/pyimod03_ctypes.pyc b/build/pointcab_renamer/localpycs/pyimod03_ctypes.pyc new file mode 100644 index 0000000..1ac086e Binary files /dev/null and b/build/pointcab_renamer/localpycs/pyimod03_ctypes.pyc differ diff --git a/build/pointcab_renamer/localpycs/struct.pyc b/build/pointcab_renamer/localpycs/struct.pyc new file mode 100644 index 0000000..ae926c1 Binary files /dev/null and b/build/pointcab_renamer/localpycs/struct.pyc differ diff --git a/build/pointcab_renamer/pointcab_renamer.pkg b/build/pointcab_renamer/pointcab_renamer.pkg new file mode 100644 index 0000000..2e7fc1c Binary files /dev/null and b/build/pointcab_renamer/pointcab_renamer.pkg differ diff --git a/build/pointcab_renamer/warn-pointcab_renamer.txt b/build/pointcab_renamer/warn-pointcab_renamer.txt new file mode 100644 index 0000000..809aab0 --- /dev/null +++ b/build/pointcab_renamer/warn-pointcab_renamer.txt @@ -0,0 +1,29 @@ + +This file lists modules PyInstaller was not able to find. This does not +necessarily mean these modules are required for running your program. Both +Python's standard library and 3rd-party Python packages often conditionally +import optional modules, some of which may be available only on certain +platforms. + +Types of import: +* top-level: imported at the top-level - look at these first +* conditional: imported within an if-statement +* delayed: imported within a function +* optional: imported within a try-except-statement + +IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for + tracking down the missing module yourself. Thanks! + +missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional) +excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional) +missing module named winreg - imported by importlib._bootstrap_external (conditional), platform (delayed, optional), mimetypes (optional), urllib.request (delayed, conditional, optional) +missing module named nt - imported by os (delayed, conditional, optional), ntpath (optional), shutil (conditional), importlib._bootstrap_external (conditional) +missing module named org - imported by pickle (optional) +missing module named _winapi - imported by encodings (delayed, conditional, optional), ntpath (optional), subprocess (conditional), mimetypes (optional) +missing module named 'org.python' - imported by copy (optional), xml.sax (delayed, conditional) +missing module named msvcrt - imported by subprocess (optional), getpass (optional) +missing module named vms_lib - imported by platform (delayed, optional) +missing module named 'java.lang' - imported by platform (delayed, optional), xml.sax._exceptions (conditional) +missing module named java - imported by platform (delayed) +missing module named _winreg - imported by platform (delayed, optional) +missing module named _scproxy - imported by urllib.request (conditional) diff --git a/build/pointcab_renamer/xref-pointcab_renamer.html b/build/pointcab_renamer/xref-pointcab_renamer.html new file mode 100644 index 0000000..734fc53 --- /dev/null +++ b/build/pointcab_renamer/xref-pointcab_renamer.html @@ -0,0 +1,8302 @@ + + + + + modulegraph cross reference for pointcab_renamer.py, pyi_rth__tkinter.py, pyi_rth_inspect.py + + + +

modulegraph cross reference for pointcab_renamer.py, pyi_rth__tkinter.py, pyi_rth_inspect.py

+ +
+ + pointcab_renamer.py +Script
+imports: + _collections_abc + • _weakrefset + • abc + • codecs + • collections + • collections.abc + • copy + • copyreg + • datetime + • encodings + • encodings.aliases + • encodings.ascii + • encodings.base64_codec + • encodings.big5 + • encodings.big5hkscs + • encodings.bz2_codec + • encodings.charmap + • encodings.cp037 + • encodings.cp1006 + • encodings.cp1026 + • encodings.cp1125 + • encodings.cp1140 + • encodings.cp1250 + • encodings.cp1251 + • encodings.cp1252 + • encodings.cp1253 + • encodings.cp1254 + • encodings.cp1255 + • encodings.cp1256 + • encodings.cp1257 + • encodings.cp1258 + • encodings.cp273 + • encodings.cp424 + • encodings.cp437 + • encodings.cp500 + • encodings.cp720 + • encodings.cp737 + • encodings.cp775 + • encodings.cp850 + • encodings.cp852 + • encodings.cp855 + • encodings.cp856 + • encodings.cp857 + • encodings.cp858 + • encodings.cp860 + • encodings.cp861 + • encodings.cp862 + • encodings.cp863 + • encodings.cp864 + • encodings.cp865 + • encodings.cp866 + • encodings.cp869 + • encodings.cp874 + • encodings.cp875 + • encodings.cp932 + • encodings.cp949 + • encodings.cp950 + • encodings.euc_jis_2004 + • encodings.euc_jisx0213 + • encodings.euc_jp + • encodings.euc_kr + • encodings.gb18030 + • encodings.gb2312 + • encodings.gbk + • encodings.hex_codec + • encodings.hp_roman8 + • encodings.hz + • encodings.idna + • encodings.iso2022_jp + • encodings.iso2022_jp_1 + • encodings.iso2022_jp_2 + • encodings.iso2022_jp_2004 + • encodings.iso2022_jp_3 + • encodings.iso2022_jp_ext + • encodings.iso2022_kr + • encodings.iso8859_1 + • encodings.iso8859_10 + • encodings.iso8859_11 + • encodings.iso8859_13 + • encodings.iso8859_14 + • encodings.iso8859_15 + • encodings.iso8859_16 + • encodings.iso8859_2 + • encodings.iso8859_3 + • encodings.iso8859_4 + • encodings.iso8859_5 + • encodings.iso8859_6 + • encodings.iso8859_7 + • encodings.iso8859_8 + • encodings.iso8859_9 + • encodings.johab + • encodings.koi8_r + • encodings.koi8_t + • encodings.koi8_u + • encodings.kz1048 + • encodings.latin_1 + • encodings.mac_arabic + • encodings.mac_croatian + • encodings.mac_cyrillic + • encodings.mac_farsi + • encodings.mac_greek + • encodings.mac_iceland + • encodings.mac_latin2 + • encodings.mac_roman + • encodings.mac_romanian + • encodings.mac_turkish + • encodings.mbcs + • encodings.oem + • encodings.palmos + • encodings.ptcp154 + • encodings.punycode + • encodings.quopri_codec + • encodings.raw_unicode_escape + • encodings.rot_13 + • encodings.shift_jis + • encodings.shift_jis_2004 + • encodings.shift_jisx0213 + • encodings.tis_620 + • encodings.undefined + • encodings.unicode_escape + • encodings.utf_16 + • encodings.utf_16_be + • encodings.utf_16_le + • encodings.utf_32 + • encodings.utf_32_be + • encodings.utf_32_le + • encodings.utf_7 + • encodings.utf_8 + • encodings.utf_8_sig + • encodings.uu_codec + • encodings.zlib_codec + • enum + • functools + • genericpath + • heapq + • io + • keyword + • linecache + • locale + • logging + • ntpath + • operator + • os + • pathlib + • posixpath + • pyi_rth__tkinter.py + • pyi_rth_inspect.py + • re + • re._casefix + • re._compiler + • re._constants + • re._parser + • reprlib + • shutil + • sre_compile + • sre_constants + • sre_parse + • stat + • tkinter + • tkinter.filedialog + • tkinter.messagebox + • tkinter.scrolledtext + • tkinter.ttk + • traceback + • types + • uuid + • warnings + • weakref + • xml.etree.ElementTree + +
+ +
+ +
+ + pyi_rth__tkinter.py +Script
+imports: + os + • sys + +
+
+imported by: + pointcab_renamer.py + +
+ +
+ +
+ + pyi_rth_inspect.py +Script
+imports: + inspect + • os + • sys + • zipfile + +
+
+imported by: + pointcab_renamer.py + +
+ +
+ +
+ + 'java.lang' +MissingModule
+imported by: + platform + • xml.sax._exceptions + +
+ +
+ +
+ + 'org.python' +MissingModule
+imported by: + copy + • xml.sax + +
+ +
+ +
+ + _abc (builtin module)
+imported by: + abc + +
+ +
+ +
+ + _ast (builtin module)
+imported by: + ast + +
+ +
+ +
+ + _bisect /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_bisect.cpython-311-x86_64-linux-gnu.so
+imported by: + bisect + +
+ +
+ +
+ + _blake2 /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_blake2.cpython-311-x86_64-linux-gnu.so
+imported by: + hashlib + +
+ +
+ +
+ + _bz2 /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_bz2.cpython-311-x86_64-linux-gnu.so
+imported by: + bz2 + +
+ +
+ +
+ + _codecs (builtin module)
+imported by: + codecs + +
+ +
+ +
+ + _codecs_cn /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_cn.cpython-311-x86_64-linux-gnu.so
+imported by: + encodings.gb18030 + • encodings.gb2312 + • encodings.gbk + • encodings.hz + +
+ +
+ +
+ + _codecs_hk /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_hk.cpython-311-x86_64-linux-gnu.so
+imported by: + encodings.big5hkscs + +
+ +
+ +
+ + _codecs_iso2022 /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_iso2022.cpython-311-x86_64-linux-gnu.so + +
+ +
+ + _codecs_jp /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_jp.cpython-311-x86_64-linux-gnu.so + +
+ +
+ + _codecs_kr /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_kr.cpython-311-x86_64-linux-gnu.so
+imported by: + encodings.cp949 + • encodings.euc_kr + • encodings.johab + +
+ +
+ +
+ + _codecs_tw /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_codecs_tw.cpython-311-x86_64-linux-gnu.so
+imported by: + encodings.big5 + • encodings.cp950 + +
+ +
+ +
+ + _collections (builtin module)
+imported by: + collections + • threading + +
+ +
+ +
+ + _collections_abc +SourceModule
+imports: + abc + • sys + +
+
+imported by: + collections + • collections.abc + • contextlib + • locale + • os + • pathlib + • pointcab_renamer.py + • random + • types + • weakref + +
+ +
+ +
+ + _compat_pickle +SourceModule
+imported by: + _pickle + • pickle + +
+ +
+ +
+ + _compression +SourceModule
+imports: + io + • sys + +
+
+imported by: + bz2 + • gzip + • lzma + +
+ +
+ +
+ + _contextvars /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_contextvars.cpython-311-x86_64-linux-gnu.so
+imported by: + contextvars + +
+ +
+ +
+ + _csv /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_csv.cpython-311-x86_64-linux-gnu.so
+imported by: + csv + +
+ +
+ +
+ + _datetime /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_datetime.cpython-311-x86_64-linux-gnu.so
+imports: + _strptime + • time + +
+
+imported by: + datetime + +
+ +
+ +
+ + _decimal /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_decimal.cpython-311-x86_64-linux-gnu.so
+imported by: + decimal + +
+ +
+ +
+ + _elementtree /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_elementtree.cpython-311-x86_64-linux-gnu.so +
+imported by: + xml.etree.ElementTree + +
+ +
+ +
+ + _frozen_importlib +ExcludedModule
+imported by: + importlib + • importlib.abc + +
+ +
+ +
+ + _frozen_importlib_external +MissingModule
+imported by: + importlib + • importlib._bootstrap + • importlib.abc + +
+ +
+ +
+ + _functools (builtin module)
+imported by: + functools + +
+ +
+ +
+ + _hashlib /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_hashlib.cpython-311-x86_64-linux-gnu.so
+imported by: + hashlib + +
+ +
+ +
+ + _heapq /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_heapq.cpython-311-x86_64-linux-gnu.so
+imported by: + heapq + +
+ +
+ +
+ + _imp (builtin module)
+imported by: + importlib + • importlib._bootstrap_external + • importlib.util + +
+ +
+ +
+ + _io (builtin module)
+imported by: + importlib._bootstrap_external + • io + +
+ +
+ +
+ + _locale (builtin module)
+imported by: + locale + +
+ +
+ +
+ + _lzma /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_lzma.cpython-311-x86_64-linux-gnu.so
+imported by: + lzma + +
+ +
+ +
+ + _md5 /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_md5.cpython-311-x86_64-linux-gnu.so
+imported by: + hashlib + +
+ +
+ +
+ + _multibytecodec /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_multibytecodec.cpython-311-x86_64-linux-gnu.so + +
+ +
+ + _opcode /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_opcode.cpython-311-x86_64-linux-gnu.so
+imported by: + opcode + +
+ +
+ +
+ + _operator (builtin module)
+imported by: + operator + +
+ +
+ +
+ + _pickle /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_pickle.cpython-311-x86_64-linux-gnu.so
+imports: + _compat_pickle + • codecs + • copyreg + +
+
+imported by: + pickle + +
+ +
+ +
+ + _posixsubprocess /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_posixsubprocess.cpython-311-x86_64-linux-gnu.so
+imports: + gc + +
+
+imported by: + subprocess + +
+ +
+ +
+ + _py_abc +SourceModule
+imports: + _weakrefset + +
+
+imported by: + abc + +
+ +
+ +
+ + _pydecimal +SourceModule
+imports: + collections + • contextvars + • itertools + • locale + • math + • numbers + • re + • sys + +
+
+imported by: + decimal + +
+ +
+ +
+ + _random /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_random.cpython-311-x86_64-linux-gnu.so
+imported by: + random + +
+ +
+ +
+ + _scproxy +MissingModule
+imported by: + urllib.request + +
+ +
+ +
+ + _sha1 /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha1.cpython-311-x86_64-linux-gnu.so
+imported by: + hashlib + +
+ +
+ +
+ + _sha256 /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha256.cpython-311-x86_64-linux-gnu.so
+imported by: + hashlib + +
+ +
+ +
+ + _sha3 /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha3.cpython-311-x86_64-linux-gnu.so
+imported by: + hashlib + +
+ +
+ +
+ + _sha512 /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_sha512.cpython-311-x86_64-linux-gnu.so
+imported by: + hashlib + • random + +
+ +
+ +
+ + _signal (builtin module)
+imported by: + signal + +
+ +
+ +
+ + _socket /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_socket.cpython-311-x86_64-linux-gnu.so
+imported by: + socket + +
+ +
+ +
+ + _sre (builtin module)
+imports: + copy + • re + +
+
+imported by: + re._compiler + • re._constants + +
+ +
+ +
+ + _ssl /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_ssl.cpython-311-x86_64-linux-gnu.so
+imports: + socket + +
+
+imported by: + ssl + +
+ +
+ +
+ + _stat (builtin module)
+imported by: + stat + +
+ +
+ +
+ + _statistics /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_statistics.cpython-311-x86_64-linux-gnu.so
+imported by: + statistics + +
+ +
+ +
+ + _string (builtin module)
+imported by: + string + +
+ +
+ +
+ + _strptime +SourceModule
+imports: + _thread + • calendar + • datetime + • locale + • re + • time + +
+
+imported by: + _datetime + • datetime + • time + +
+ +
+ +
+ + _struct /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_struct.cpython-311-x86_64-linux-gnu.so
+imported by: + struct + +
+ +
+ +
+ + _thread (builtin module)
+imported by: + _strptime + • dataclasses + • functools + • reprlib + • tempfile + • threading + +
+ +
+ +
+ + _threading_local +SourceModule
+imports: + contextlib + • threading + • weakref + +
+
+imported by: + threading + +
+ +
+ +
+ + _tkinter /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_tkinter.cpython-311-x86_64-linux-gnu.so
+imported by: + tkinter + +
+ +
+ +
+ + _tokenize (builtin module)
+imported by: + tokenize + +
+ +
+ +
+ + _tracemalloc (builtin module)
+imported by: + tracemalloc + +
+ +
+ +
+ + _typing /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_typing.cpython-311-x86_64-linux-gnu.so
+imported by: + typing + +
+ +
+ +
+ + _uuid /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/_uuid.cpython-311-x86_64-linux-gnu.so
+imported by: + uuid + +
+ +
+ +
+ + _warnings (builtin module)
+imported by: + importlib._bootstrap_external + • warnings + +
+ +
+ +
+ + _weakref (builtin module)
+imported by: + _weakrefset + • collections + • weakref + • xml.sax.expatreader + +
+ +
+ +
+ + _weakrefset +SourceModule
+imports: + _weakref + • types + +
+
+imported by: + _py_abc + • pointcab_renamer.py + • threading + • weakref + +
+ +
+ +
+ + _winapi +MissingModule
+imported by: + encodings + • mimetypes + • ntpath + • subprocess + +
+ +
+ +
+ + _winreg +MissingModule
+imported by: + platform + +
+ +
+ +
+ + abc +SourceModule
+imports: + _abc + • _py_abc + +
+
+imported by: + _collections_abc + • contextlib + • dataclasses + • email._policybase + • functools + • importlib._abc + • importlib.abc + • importlib.metadata + • importlib.resources.abc + • inspect + • io + • numbers + • os + • pointcab_renamer.py + • selectors + • typing + +
+ +
+ +
+ + argparse +SourceModule
+imports: + copy + • gettext + • os + • re + • shutil + • sys + • textwrap + • warnings + +
+
+imported by: + ast + • calendar + • dis + • gzip + • inspect + • py_compile + • tarfile + • tokenize + • zipfile + +
+ +
+ +
+ + array /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/array.cpython-311-x86_64-linux-gnu.so
+imported by: + socket + +
+ +
+ +
+ + ast +SourceModule
+imports: + _ast + • argparse + • collections + • contextlib + • enum + • inspect + • sys + • warnings + +
+
+imported by: + inspect + • traceback + +
+ +
+ +
+ + atexit (builtin module)
+imported by: + logging + • weakref + +
+ +
+ +
+ + base64 +SourceModule
+imports: + binascii + • getopt + • re + • struct + • sys + +
+ + +
+ +
+ + binascii /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/binascii.cpython-311-x86_64-linux-gnu.so + +
+ +
+ + bisect +SourceModule
+imports: + _bisect + +
+
+imported by: + random + • statistics + • urllib.request + +
+ +
+ +
+ + builtins (builtin module)
+imported by: + bz2 + • codecs + • dataclasses + • enum + • gettext + • gzip + • inspect + • locale + • lzma + • operator + • reprlib + • subprocess + • tarfile + • tokenize + • warnings + +
+ +
+ +
+ + bz2 +SourceModule
+imports: + _bz2 + • _compression + • builtins + • io + • os + +
+
+imported by: + encodings.bz2_codec + • shutil + • tarfile + • zipfile + +
+ +
+ +
+ + calendar +SourceModule
+imports: + argparse + • datetime + • itertools + • locale + • sys + +
+
+imported by: + _strptime + • email._parseaddr + • http.cookiejar + • ssl + +
+ +
+ +
+ + codecs +SourceModule
+imports: + _codecs + • builtins + • encodings + • sys + +
+
+imported by: + _pickle + • encodings + • encodings.ascii + • encodings.base64_codec + • encodings.big5 + • encodings.big5hkscs + • encodings.bz2_codec + • encodings.charmap + • encodings.cp037 + • encodings.cp1006 + • encodings.cp1026 + • encodings.cp1125 + • encodings.cp1140 + • encodings.cp1250 + • encodings.cp1251 + • encodings.cp1252 + • encodings.cp1253 + • encodings.cp1254 + • encodings.cp1255 + • encodings.cp1256 + • encodings.cp1257 + • encodings.cp1258 + • encodings.cp273 + • encodings.cp424 + • encodings.cp437 + • encodings.cp500 + • encodings.cp720 + • encodings.cp737 + • encodings.cp775 + • encodings.cp850 + • encodings.cp852 + • encodings.cp855 + • encodings.cp856 + • encodings.cp857 + • encodings.cp858 + • encodings.cp860 + • encodings.cp861 + • encodings.cp862 + • encodings.cp863 + • encodings.cp864 + • encodings.cp865 + • encodings.cp866 + • encodings.cp869 + • encodings.cp874 + • encodings.cp875 + • encodings.cp932 + • encodings.cp949 + • encodings.cp950 + • encodings.euc_jis_2004 + • encodings.euc_jisx0213 + • encodings.euc_jp + • encodings.euc_kr + • encodings.gb18030 + • encodings.gb2312 + • encodings.gbk + • encodings.hex_codec + • encodings.hp_roman8 + • encodings.hz + • encodings.idna + • encodings.iso2022_jp + • encodings.iso2022_jp_1 + • encodings.iso2022_jp_2 + • encodings.iso2022_jp_2004 + • encodings.iso2022_jp_3 + • encodings.iso2022_jp_ext + • encodings.iso2022_kr + • encodings.iso8859_1 + • encodings.iso8859_10 + • encodings.iso8859_11 + • encodings.iso8859_13 + • encodings.iso8859_14 + • encodings.iso8859_15 + • encodings.iso8859_16 + • encodings.iso8859_2 + • encodings.iso8859_3 + • encodings.iso8859_4 + • encodings.iso8859_5 + • encodings.iso8859_6 + • encodings.iso8859_7 + • encodings.iso8859_8 + • encodings.iso8859_9 + • encodings.johab + • encodings.koi8_r + • encodings.koi8_t + • encodings.koi8_u + • encodings.kz1048 + • encodings.latin_1 + • encodings.mac_arabic + • encodings.mac_croatian + • encodings.mac_cyrillic + • encodings.mac_farsi + • encodings.mac_greek + • encodings.mac_iceland + • encodings.mac_latin2 + • encodings.mac_roman + • encodings.mac_romanian + • encodings.mac_turkish + • encodings.mbcs + • encodings.oem + • encodings.palmos + • encodings.ptcp154 + • encodings.punycode + • encodings.quopri_codec + • encodings.raw_unicode_escape + • encodings.rot_13 + • encodings.shift_jis + • encodings.shift_jis_2004 + • encodings.shift_jisx0213 + • encodings.tis_620 + • encodings.undefined + • encodings.unicode_escape + • encodings.utf_16 + • encodings.utf_16_be + • encodings.utf_16_le + • encodings.utf_32 + • encodings.utf_32_be + • encodings.utf_32_le + • encodings.utf_7 + • encodings.utf_8 + • encodings.utf_8_sig + • encodings.uu_codec + • encodings.zlib_codec + • pickle + • pointcab_renamer.py + • tokenize + • xml.sax.saxutils + +
+ +
+ +
+ + collections +Package
+imports: + _collections + • _collections_abc + • _weakref + • copy + • heapq + • itertools + • keyword + • operator + • reprlib + • sys + +
+
+imported by: + _pydecimal + • ast + • collections.abc + • contextlib + • dis + • email.feedparser + • functools + • importlib.metadata + • importlib.metadata._collections + • importlib.resources.readers + • inspect + • platform + • pointcab_renamer.py + • pprint + • selectors + • shlex + • shutil + • ssl + • statistics + • string + • threading + • tkinter + • tokenize + • typing + • urllib.parse + • xml.etree.ElementTree + +
+ +
+ +
+ + collections.abc +SourceModule
+imports: + _collections_abc + • collections + +
+
+imported by: + http.client + • inspect + • logging + • pointcab_renamer.py + • selectors + • traceback + • tracemalloc + • typing + • xml.etree.ElementTree + +
+ +
+ +
+ + contextlib +SourceModule
+imports: + _collections_abc + • abc + • collections + • functools + • os + • sys + • types + +
+ + +
+ +
+ + contextvars +SourceModule
+imports: + _contextvars + +
+
+imported by: + _pydecimal + +
+ +
+ +
+ + copy +SourceModule
+imports: + 'org.python' + • copyreg + • types + • weakref + +
+
+imported by: + _sre + • argparse + • collections + • dataclasses + • email.generator + • gettext + • http.cookiejar + • pointcab_renamer.py + • tarfile + • weakref + • xml.etree.ElementInclude + +
+ +
+ +
+ + copyreg +SourceModule
+imports: + functools + • operator + +
+
+imported by: + _pickle + • copy + • pickle + • pointcab_renamer.py + • re + +
+ +
+ +
+ + csv +SourceModule
+imports: + _csv + • io + • re + +
+
+imported by: + importlib.metadata + +
+ +
+ +
+ + dataclasses +SourceModule
+imports: + _thread + • abc + • builtins + • copy + • functools + • inspect + • itertools + • keyword + • re + • sys + • types + +
+
+imported by: + pprint + +
+ +
+ +
+ + datetime +SourceModule
+imports: + _datetime + • _strptime + • math + • operator + • sys + • time + +
+
+imported by: + _strptime + • calendar + • email.utils + • http.cookiejar + • pointcab_renamer.py + +
+ +
+ +
+ + decimal +SourceModule
+imports: + _decimal + • _pydecimal + +
+
+imported by: + fractions + • statistics + +
+ +
+ +
+ + dis +SourceModule
+imports: + argparse + • collections + • io + • opcode + • sys + • types + +
+
+imported by: + inspect + +
+ +
+ + + +
+ + email._encoded_words +SourceModule
+imports: + base64 + • binascii + • email + • email.errors + • functools + • re + • string + +
+
+imported by: + email._header_value_parser + • email.message + +
+ +
+ +
+ + email._header_value_parser +SourceModule
+imports: + email + • email._encoded_words + • email.errors + • email.utils + • operator + • re + • string + • sys + • urllib + +
+
+imported by: + email + • email.headerregistry + +
+ +
+ +
+ + email._parseaddr +SourceModule
+imports: + calendar + • email + • time + +
+
+imported by: + email.utils + +
+ +
+ +
+ + email._policybase +SourceModule
+imports: + abc + • email + • email.charset + • email.header + • email.utils + +
+
+imported by: + email.feedparser + • email.message + • email.parser + • email.policy + +
+ +
+ +
+ + email.base64mime +SourceModule
+imports: + base64 + • binascii + • email + +
+
+imported by: + email.charset + • email.header + +
+ +
+ +
+ + email.charset +SourceModule
+imports: + email + • email.base64mime + • email.encoders + • email.errors + • email.quoprimime + • functools + +
+
+imported by: + email + • email._policybase + • email.contentmanager + • email.header + • email.message + • email.utils + +
+ +
+ +
+ + email.contentmanager +SourceModule
+imports: + binascii + • email + • email.charset + • email.errors + • email.message + • email.quoprimime + +
+
+imported by: + email.policy + +
+ +
+ +
+ + email.encoders +SourceModule
+imports: + base64 + • email + • quopri + +
+
+imported by: + email.charset + +
+ +
+ +
+ + email.errors +SourceModule
+imports: + email + +
+ + +
+ +
+ + email.feedparser +SourceModule
+imports: + collections + • email + • email._policybase + • email.errors + • email.message + • io + • re + +
+
+imported by: + email.parser + +
+ +
+ +
+ + email.generator +SourceModule
+imports: + copy + • email + • email.utils + • io + • random + • re + • sys + • time + +
+
+imported by: + email.message + +
+ +
+ +
+ + email.header +SourceModule
+imports: + binascii + • email + • email.base64mime + • email.charset + • email.errors + • email.quoprimime + • re + +
+
+imported by: + email + • email._policybase + +
+ +
+ +
+ + email.headerregistry +SourceModule
+imports: + email + • email._header_value_parser + • email.errors + • email.utils + • types + +
+
+imported by: + email.policy + +
+ +
+ +
+ + email.iterators +SourceModule
+imports: + email + • io + • sys + +
+
+imported by: + email.message + +
+ +
+ +
+ + email.message +SourceModule
+imports: + binascii + • email + • email._encoded_words + • email._policybase + • email.charset + • email.errors + • email.generator + • email.iterators + • email.policy + • email.utils + • io + • quopri + • re + +
+ + +
+ +
+ + email.parser +SourceModule
+imports: + email + • email._policybase + • email.feedparser + • io + +
+
+imported by: + email + • http.client + +
+ +
+ +
+ + email.policy +SourceModule
+imports: + email + • email._policybase + • email.contentmanager + • email.headerregistry + • email.message + • email.utils + • re + • sys + +
+
+imported by: + email.message + +
+ +
+ +
+ + email.quoprimime +SourceModule
+imports: + email + • re + • string + +
+
+imported by: + email.charset + • email.contentmanager + • email.header + +
+ +
+ +
+ + email.utils +SourceModule
+imports: + datetime + • email + • email._parseaddr + • email.charset + • os + • random + • re + • socket + • time + • urllib.parse + +
+ + +
+ +
+ + encodings +Package
+imports: + _winapi + • codecs + • encodings + • encodings.aliases + • encodings.ascii + • encodings.base64_codec + • encodings.big5 + • encodings.big5hkscs + • encodings.bz2_codec + • encodings.charmap + • encodings.cp037 + • encodings.cp1006 + • encodings.cp1026 + • encodings.cp1125 + • encodings.cp1140 + • encodings.cp1250 + • encodings.cp1251 + • encodings.cp1252 + • encodings.cp1253 + • encodings.cp1254 + • encodings.cp1255 + • encodings.cp1256 + • encodings.cp1257 + • encodings.cp1258 + • encodings.cp273 + • encodings.cp424 + • encodings.cp437 + • encodings.cp500 + • encodings.cp720 + • encodings.cp737 + • encodings.cp775 + • encodings.cp850 + • encodings.cp852 + • encodings.cp855 + • encodings.cp856 + • encodings.cp857 + • encodings.cp858 + • encodings.cp860 + • encodings.cp861 + • encodings.cp862 + • encodings.cp863 + • encodings.cp864 + • encodings.cp865 + • encodings.cp866 + • encodings.cp869 + • encodings.cp874 + • encodings.cp875 + • encodings.cp932 + • encodings.cp949 + • encodings.cp950 + • encodings.euc_jis_2004 + • encodings.euc_jisx0213 + • encodings.euc_jp + • encodings.euc_kr + • encodings.gb18030 + • encodings.gb2312 + • encodings.gbk + • encodings.hex_codec + • encodings.hp_roman8 + • encodings.hz + • encodings.idna + • encodings.iso2022_jp + • encodings.iso2022_jp_1 + • encodings.iso2022_jp_2 + • encodings.iso2022_jp_2004 + • encodings.iso2022_jp_3 + • encodings.iso2022_jp_ext + • encodings.iso2022_kr + • encodings.iso8859_1 + • encodings.iso8859_10 + • encodings.iso8859_11 + • encodings.iso8859_13 + • encodings.iso8859_14 + • encodings.iso8859_15 + • encodings.iso8859_16 + • encodings.iso8859_2 + • encodings.iso8859_3 + • encodings.iso8859_4 + • encodings.iso8859_5 + • encodings.iso8859_6 + • encodings.iso8859_7 + • encodings.iso8859_8 + • encodings.iso8859_9 + • encodings.johab + • encodings.koi8_r + • encodings.koi8_t + • encodings.koi8_u + • encodings.kz1048 + • encodings.latin_1 + • encodings.mac_arabic + • encodings.mac_croatian + • encodings.mac_cyrillic + • encodings.mac_farsi + • encodings.mac_greek + • encodings.mac_iceland + • encodings.mac_latin2 + • encodings.mac_roman + • encodings.mac_romanian + • encodings.mac_turkish + • encodings.mbcs + • encodings.oem + • encodings.palmos + • encodings.ptcp154 + • encodings.punycode + • encodings.quopri_codec + • encodings.raw_unicode_escape + • encodings.rot_13 + • encodings.shift_jis + • encodings.shift_jis_2004 + • encodings.shift_jisx0213 + • encodings.tis_620 + • encodings.undefined + • encodings.unicode_escape + • encodings.utf_16 + • encodings.utf_16_be + • encodings.utf_16_le + • encodings.utf_32 + • encodings.utf_32_be + • encodings.utf_32_le + • encodings.utf_7 + • encodings.utf_8 + • encodings.utf_8_sig + • encodings.uu_codec + • encodings.zlib_codec + • sys + +
+
+imported by: + codecs + • encodings + • encodings.aliases + • encodings.ascii + • encodings.base64_codec + • encodings.big5 + • encodings.big5hkscs + • encodings.bz2_codec + • encodings.charmap + • encodings.cp037 + • encodings.cp1006 + • encodings.cp1026 + • encodings.cp1125 + • encodings.cp1140 + • encodings.cp1250 + • encodings.cp1251 + • encodings.cp1252 + • encodings.cp1253 + • encodings.cp1254 + • encodings.cp1255 + • encodings.cp1256 + • encodings.cp1257 + • encodings.cp1258 + • encodings.cp273 + • encodings.cp424 + • encodings.cp437 + • encodings.cp500 + • encodings.cp720 + • encodings.cp737 + • encodings.cp775 + • encodings.cp850 + • encodings.cp852 + • encodings.cp855 + • encodings.cp856 + • encodings.cp857 + • encodings.cp858 + • encodings.cp860 + • encodings.cp861 + • encodings.cp862 + • encodings.cp863 + • encodings.cp864 + • encodings.cp865 + • encodings.cp866 + • encodings.cp869 + • encodings.cp874 + • encodings.cp875 + • encodings.cp932 + • encodings.cp949 + • encodings.cp950 + • encodings.euc_jis_2004 + • encodings.euc_jisx0213 + • encodings.euc_jp + • encodings.euc_kr + • encodings.gb18030 + • encodings.gb2312 + • encodings.gbk + • encodings.hex_codec + • encodings.hp_roman8 + • encodings.hz + • encodings.idna + • encodings.iso2022_jp + • encodings.iso2022_jp_1 + • encodings.iso2022_jp_2 + • encodings.iso2022_jp_2004 + • encodings.iso2022_jp_3 + • encodings.iso2022_jp_ext + • encodings.iso2022_kr + • encodings.iso8859_1 + • encodings.iso8859_10 + • encodings.iso8859_11 + • encodings.iso8859_13 + • encodings.iso8859_14 + • encodings.iso8859_15 + • encodings.iso8859_16 + • encodings.iso8859_2 + • encodings.iso8859_3 + • encodings.iso8859_4 + • encodings.iso8859_5 + • encodings.iso8859_6 + • encodings.iso8859_7 + • encodings.iso8859_8 + • encodings.iso8859_9 + • encodings.johab + • encodings.koi8_r + • encodings.koi8_t + • encodings.koi8_u + • encodings.kz1048 + • encodings.latin_1 + • encodings.mac_arabic + • encodings.mac_croatian + • encodings.mac_cyrillic + • encodings.mac_farsi + • encodings.mac_greek + • encodings.mac_iceland + • encodings.mac_latin2 + • encodings.mac_roman + • encodings.mac_romanian + • encodings.mac_turkish + • encodings.mbcs + • encodings.oem + • encodings.palmos + • encodings.ptcp154 + • encodings.punycode + • encodings.quopri_codec + • encodings.raw_unicode_escape + • encodings.rot_13 + • encodings.shift_jis + • encodings.shift_jis_2004 + • encodings.shift_jisx0213 + • encodings.tis_620 + • encodings.undefined + • encodings.unicode_escape + • encodings.utf_16 + • encodings.utf_16_be + • encodings.utf_16_le + • encodings.utf_32 + • encodings.utf_32_be + • encodings.utf_32_le + • encodings.utf_7 + • encodings.utf_8 + • encodings.utf_8_sig + • encodings.uu_codec + • encodings.zlib_codec + • locale + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.aliases +SourceModule
+imports: + encodings + +
+
+imported by: + encodings + • locale + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.ascii +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.base64_codec +SourceModule
+imports: + base64 + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.big5 +SourceModule
+imports: + _codecs_tw + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.big5hkscs +SourceModule
+imports: + _codecs_hk + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.bz2_codec +SourceModule
+imports: + bz2 + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.charmap +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp037 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1006 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1026 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1125 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1140 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1250 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1251 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1252 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1253 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1254 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1255 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1256 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1257 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp1258 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp273 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp424 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp437 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp500 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp720 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp737 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp775 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp850 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp852 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp855 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp856 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp857 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp858 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp860 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp861 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp862 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp863 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp864 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp865 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp866 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp869 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp874 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp875 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp932 +SourceModule
+imports: + _codecs_jp + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp949 +SourceModule
+imports: + _codecs_kr + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.cp950 +SourceModule
+imports: + _codecs_tw + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.euc_jis_2004 +SourceModule
+imports: + _codecs_jp + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.euc_jisx0213 +SourceModule
+imports: + _codecs_jp + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.euc_jp +SourceModule
+imports: + _codecs_jp + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.euc_kr +SourceModule
+imports: + _codecs_kr + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.gb18030 +SourceModule
+imports: + _codecs_cn + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.gb2312 +SourceModule
+imports: + _codecs_cn + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.gbk +SourceModule
+imports: + _codecs_cn + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.hex_codec +SourceModule
+imports: + binascii + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.hp_roman8 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.hz +SourceModule
+imports: + _codecs_cn + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.idna +SourceModule
+imports: + codecs + • encodings + • re + • stringprep + • unicodedata + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso2022_jp +SourceModule
+imports: + _codecs_iso2022 + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso2022_jp_1 +SourceModule
+imports: + _codecs_iso2022 + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso2022_jp_2 +SourceModule
+imports: + _codecs_iso2022 + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso2022_jp_2004 +SourceModule
+imports: + _codecs_iso2022 + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso2022_jp_3 +SourceModule
+imports: + _codecs_iso2022 + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso2022_jp_ext +SourceModule
+imports: + _codecs_iso2022 + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso2022_kr +SourceModule
+imports: + _codecs_iso2022 + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_1 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_10 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_11 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_13 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_14 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_15 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_16 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_2 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_3 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_4 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_5 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_6 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_7 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_8 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.iso8859_9 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.johab +SourceModule
+imports: + _codecs_kr + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.koi8_r +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.koi8_t +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.koi8_u +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.kz1048 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.latin_1 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_arabic +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_croatian +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_cyrillic +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_farsi +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_greek +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_iceland +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_latin2 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_roman +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_romanian +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mac_turkish +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.mbcs +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.oem +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.palmos +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.ptcp154 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.punycode +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.quopri_codec +SourceModule
+imports: + codecs + • encodings + • io + • quopri + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.raw_unicode_escape +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.rot_13 +SourceModule
+imports: + codecs + • encodings + • sys + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.shift_jis +SourceModule
+imports: + _codecs_jp + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.shift_jis_2004 +SourceModule
+imports: + _codecs_jp + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.shift_jisx0213 +SourceModule
+imports: + _codecs_jp + • _multibytecodec + • codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.tis_620 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.undefined +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.unicode_escape +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.utf_16 +SourceModule
+imports: + codecs + • encodings + • sys + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.utf_16_be +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.utf_16_le +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.utf_32 +SourceModule
+imports: + codecs + • encodings + • sys + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.utf_32_be +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.utf_32_le +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.utf_7 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.utf_8 +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.utf_8_sig +SourceModule
+imports: + codecs + • encodings + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.uu_codec +SourceModule
+imports: + binascii + • codecs + • encodings + • io + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + encodings.zlib_codec +SourceModule
+imports: + codecs + • encodings + • zlib + +
+
+imported by: + encodings + • pointcab_renamer.py + +
+ +
+ +
+ + enum +SourceModule
+imports: + builtins + • functools + • operator + • sys + • types + • warnings + +
+
+imported by: + ast + • http + • inspect + • pointcab_renamer.py + • py_compile + • re + • signal + • socket + • ssl + • tkinter + • uuid + +
+ +
+ +
+ + errno (builtin module)
+imported by: + gettext + • gzip + • http.client + • pathlib + • shutil + • socket + • ssl + • subprocess + • tempfile + +
+ +
+ +
+ + fcntl /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/fcntl.cpython-311-x86_64-linux-gnu.so
+imported by: + subprocess + +
+ +
+ +
+ + fnmatch +SourceModule
+imports: + functools + • os + • posixpath + • re + +
+
+imported by: + pathlib + • shutil + • tkinter.filedialog + • tracemalloc + • urllib.request + +
+ +
+ +
+ + fractions +SourceModule
+imports: + decimal + • math + • numbers + • operator + • re + • sys + +
+
+imported by: + statistics + +
+ +
+ +
+ + ftplib +SourceModule
+imports: + netrc + • re + • socket + • ssl + • sys + • warnings + +
+
+imported by: + urllib.request + +
+ +
+ +
+ + functools +SourceModule
+imports: + _functools + • _thread + • abc + • collections + • reprlib + • types + • typing + • weakref + +
+
+imported by: + contextlib + • copyreg + • dataclasses + • email._encoded_words + • email.charset + • enum + • fnmatch + • importlib.metadata + • importlib.metadata._functools + • importlib.resources._common + • importlib.resources._legacy + • importlib.util + • inspect + • ipaddress + • linecache + • locale + • operator + • pathlib + • pickle + • platform + • pointcab_renamer.py + • re + • statistics + • tempfile + • threading + • tokenize + • tracemalloc + • types + • typing + • urllib.parse + +
+ +
+ +
+ + gc (builtin module)
+imports: + time + +
+
+imported by: + _posixsubprocess + • weakref + +
+ +
+ +
+ + genericpath +SourceModule
+imports: + os + • stat + +
+
+imported by: + ntpath + • pointcab_renamer.py + • posixpath + +
+ +
+ +
+ + getopt +SourceModule
+imports: + gettext + • os + • sys + +
+
+imported by: + base64 + • mimetypes + • quopri + +
+ +
+ +
+ + getpass +SourceModule
+imports: + contextlib + • io + • msvcrt + • os + • pwd + • sys + • termios + • warnings + +
+
+imported by: + urllib.request + +
+ +
+ +
+ + gettext +SourceModule
+imports: + builtins + • copy + • errno + • locale + • os + • re + • struct + • sys + • warnings + +
+
+imported by: + argparse + • getopt + +
+ +
+ +
+ + grp /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/grp.cpython-311-x86_64-linux-gnu.so
+imported by: + pathlib + • shutil + • subprocess + • tarfile + +
+ +
+ +
+ + gzip +SourceModule
+imports: + _compression + • argparse + • builtins + • errno + • io + • os + • struct + • sys + • time + • warnings + • zlib + +
+
+imported by: + tarfile + +
+ +
+ +
+ + hashlib +SourceModule
+imports: + _blake2 + • _hashlib + • _md5 + • _sha1 + • _sha256 + • _sha3 + • _sha512 + • logging + • warnings + +
+
+imported by: + random + • urllib.request + • uuid + +
+ +
+ +
+ + heapq +SourceModule
+imports: + _heapq + +
+
+imported by: + collections + • pointcab_renamer.py + +
+ +
+ +
+ + http +Package
+imports: + enum + +
+
+imported by: + http.client + • http.cookiejar + +
+ +
+ +
+ + http.client +SourceModule
+imports: + collections.abc + • email.message + • email.parser + • errno + • http + • io + • re + • socket + • ssl + • sys + • urllib.parse + • warnings + +
+
+imported by: + http.cookiejar + • urllib.request + +
+ +
+ +
+ + http.cookiejar +SourceModule
+imports: + calendar + • copy + • datetime + • http + • http.client + • io + • logging + • os + • re + • threading + • time + • traceback + • urllib.parse + • urllib.request + • warnings + +
+
+imported by: + urllib.request + +
+ +
+ + + +
+ + importlib._abc +SourceModule
+imports: + abc + • importlib + • importlib._bootstrap + • warnings + +
+
+imported by: + importlib.abc + • importlib.util + +
+ +
+ +
+ + importlib._bootstrap +SourceModule
+imports: + _frozen_importlib_external + • importlib + +
+
+imported by: + importlib + • importlib._abc + • importlib.machinery + • importlib.util + +
+ +
+ +
+ + importlib._bootstrap_external +SourceModule
+imports: + _imp + • _io + • _warnings + • importlib + • importlib.metadata + • importlib.readers + • marshal + • nt + • posix + • sys + • tokenize + • winreg + +
+
+imported by: + importlib + • importlib.abc + • importlib.machinery + • importlib.util + • py_compile + +
+ +
+ + + +
+ + importlib.machinery +SourceModule +
+imported by: + importlib + • importlib.abc + • inspect + • py_compile + +
+ +
+ + + +
+ + importlib.metadata._adapters +SourceModule
+imports: + email.message + • importlib.metadata + • importlib.metadata._text + • re + • textwrap + +
+
+imported by: + importlib.metadata + +
+ +
+ +
+ + importlib.metadata._collections +SourceModule
+imports: + collections + • importlib.metadata + +
+
+imported by: + importlib.metadata + +
+ +
+ +
+ + importlib.metadata._functools +SourceModule
+imports: + functools + • importlib.metadata + • types + +
+
+imported by: + importlib.metadata + • importlib.metadata._text + +
+ +
+ +
+ + importlib.metadata._itertools +SourceModule
+imports: + importlib.metadata + • itertools + +
+
+imported by: + importlib.metadata + +
+ +
+ +
+ + importlib.metadata._meta +SourceModule
+imports: + importlib.metadata + • typing + +
+
+imported by: + importlib.metadata + +
+ +
+ +
+ + importlib.metadata._text +SourceModule +
+imported by: + importlib.metadata._adapters + +
+ +
+ +
+ + importlib.readers +SourceModule
+imports: + importlib + • importlib.resources.readers + +
+
+imported by: + importlib._bootstrap_external + +
+ +
+ + + +
+ + importlib.resources._adapters +SourceModule
+imports: + contextlib + • importlib.resources + • importlib.resources.abc + • io + +
+
+imported by: + importlib.resources._common + +
+ +
+ +
+ + importlib.resources._common +SourceModule
+imports: + contextlib + • functools + • importlib + • importlib.resources + • importlib.resources._adapters + • importlib.resources.abc + • os + • pathlib + • tempfile + • types + • typing + +
+ + +
+ +
+ + importlib.resources._itertools +SourceModule
+imports: + importlib.resources + • itertools + • typing + +
+
+imported by: + importlib.resources.readers + +
+ +
+ +
+ + importlib.resources._legacy +SourceModule
+imports: + functools + • importlib.resources + • importlib.resources._common + • os + • pathlib + • types + • typing + • warnings + +
+
+imported by: + importlib.resources + +
+ +
+ +
+ + importlib.resources.abc +SourceModule
+imports: + abc + • importlib.resources + • io + • os + • typing + +
+ + +
+ +
+ + importlib.resources.readers +SourceModule +
+imported by: + importlib.readers + +
+ +
+ +
+ + importlib.util +SourceModule
+imports: + _imp + • contextlib + • functools + • importlib + • importlib._abc + • importlib._bootstrap + • importlib._bootstrap_external + • sys + • types + • warnings + +
+
+imported by: + py_compile + • zipfile + +
+ +
+ +
+ + inspect +SourceModule
+imports: + abc + • argparse + • ast + • builtins + • collections + • collections.abc + • dis + • enum + • functools + • importlib + • importlib.machinery + • itertools + • keyword + • linecache + • operator + • os + • re + • sys + • token + • tokenize + • types + +
+
+imported by: + ast + • dataclasses + • pyi_rth_inspect.py + +
+ +
+ +
+ + io +SourceModule
+imports: + _io + • abc + • warnings + +
+
+imported by: + _compression + • bz2 + • csv + • dis + • email.feedparser + • email.generator + • email.iterators + • email.message + • email.parser + • encodings.quopri_codec + • encodings.uu_codec + • getpass + • gzip + • http.client + • http.cookiejar + • importlib.resources._adapters + • importlib.resources.abc + • logging + • lzma + • os + • pathlib + • pickle + • pointcab_renamer.py + • pprint + • quopri + • shlex + • socket + • subprocess + • tarfile + • tempfile + • tokenize + • urllib.error + • urllib.request + • uuid + • xml.etree.ElementTree + • xml.sax + • xml.sax.saxutils + • zipfile + +
+ +
+ +
+ + ipaddress +SourceModule
+imports: + functools + • re + +
+
+imported by: + urllib.parse + +
+ +
+ +
+ + itertools (builtin module)
+imported by: + _pydecimal + • calendar + • collections + • dataclasses + • importlib.metadata + • importlib.metadata._itertools + • importlib.resources._itertools + • inspect + • pickle + • platform + • random + • reprlib + • statistics + • threading + • tokenize + • traceback + • weakref + • zipfile + +
+ +
+ +
+ + java +MissingModule
+imported by: + platform + +
+ +
+ +
+ + keyword +SourceModule
+imported by: + collections + • dataclasses + • inspect + • pointcab_renamer.py + +
+ +
+ +
+ + linecache +SourceModule
+imports: + functools + • os + • sys + • tokenize + +
+
+imported by: + inspect + • pointcab_renamer.py + • traceback + • tracemalloc + • warnings + +
+ +
+ +
+ + locale +SourceModule
+imports: + _collections_abc + • _locale + • builtins + • encodings + • encodings.aliases + • functools + • os + • re + • sys + • warnings + +
+
+imported by: + _pydecimal + • _strptime + • calendar + • gettext + • pointcab_renamer.py + • subprocess + • tkinter.filedialog + +
+ +
+ +
+ + logging +Package
+imports: + atexit + • collections.abc + • io + • os + • pickle + • re + • string + • sys + • threading + • time + • traceback + • types + • warnings + • weakref + +
+
+imported by: + hashlib + • http.cookiejar + • pointcab_renamer.py + +
+ +
+ +
+ + lzma +SourceModule
+imports: + _compression + • _lzma + • builtins + • io + • os + +
+
+imported by: + shutil + • tarfile + • zipfile + +
+ +
+ +
+ + marshal (builtin module)
+imported by: + importlib._bootstrap_external + +
+ +
+ +
+ + math /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/math.cpython-311-x86_64-linux-gnu.so
+imported by: + _pydecimal + • datetime + • fractions + • random + • selectors + • statistics + +
+ +
+ +
+ + mimetypes +SourceModule
+imports: + _winapi + • getopt + • os + • posixpath + • sys + • urllib.parse + • winreg + +
+
+imported by: + urllib.request + +
+ +
+ +
+ + msvcrt +MissingModule
+imported by: + getpass + • subprocess + +
+ +
+ +
+ + netrc +SourceModule
+imports: + os + • pwd + • shlex + • stat + +
+
+imported by: + ftplib + +
+ +
+ +
+ + nt +MissingModule
+imported by: + importlib._bootstrap_external + • ntpath + • os + • shutil + +
+ +
+ +
+ + ntpath +SourceModule
+imports: + _winapi + • genericpath + • nt + • os + • stat + • string + • sys + +
+
+imported by: + os + • pathlib + • pointcab_renamer.py + +
+ +
+ +
+ + nturl2path +SourceModule
+imports: + string + • urllib.parse + +
+
+imported by: + urllib.request + +
+ +
+ +
+ + numbers +SourceModule
+imports: + abc + +
+
+imported by: + _pydecimal + • fractions + • statistics + +
+ +
+ +
+ + opcode +SourceModule
+imports: + _opcode + +
+
+imported by: + dis + +
+ +
+ +
+ + operator +SourceModule
+imports: + _operator + • builtins + • functools + +
+
+imported by: + collections + • copyreg + • datetime + • email._header_value_parser + • enum + • fractions + • importlib.metadata + • importlib.resources.readers + • inspect + • pathlib + • pointcab_renamer.py + • random + • statistics + • typing + +
+ +
+ +
+ + org +MissingModule
+imported by: + pickle + +
+ +
+ +
+ + os +SourceModule
+imports: + _collections_abc + • abc + • io + • nt + • ntpath + • os.path + • posix + • posixpath + • stat + • subprocess + • sys + • warnings + +
+
+imported by: + argparse + • bz2 + • contextlib + • email.utils + • fnmatch + • genericpath + • getopt + • getpass + • gettext + • gzip + • http.cookiejar + • importlib.metadata + • importlib.resources._common + • importlib.resources._legacy + • importlib.resources.abc + • inspect + • linecache + • locale + • logging + • lzma + • mimetypes + • netrc + • ntpath + • os.path + • pathlib + • platform + • pointcab_renamer.py + • posixpath + • py_compile + • pyi_rth__tkinter.py + • pyi_rth_inspect.py + • random + • shlex + • shutil + • socket + • ssl + • subprocess + • tarfile + • tempfile + • threading + • tkinter + • tkinter.filedialog + • urllib.request + • uuid + • xml.sax + • xml.sax.saxutils + • zipfile + +
+ +
+ +
+ + os.path +AliasNode
+imports: + os + • posixpath + +
+
+imported by: + os + • py_compile + • tracemalloc + +
+ +
+ +
+ + pathlib +SourceModule
+imports: + _collections_abc + • errno + • fnmatch + • functools + • grp + • io + • ntpath + • operator + • os + • posixpath + • pwd + • re + • stat + • sys + • urllib.parse + • warnings + +
+ + +
+ +
+ + pickle +SourceModule
+imports: + _compat_pickle + • _pickle + • codecs + • copyreg + • functools + • io + • itertools + • org + • pprint + • re + • struct + • sys + • types + +
+
+imported by: + logging + • tracemalloc + +
+ +
+ +
+ + platform +SourceModule
+imports: + 'java.lang' + • _winreg + • collections + • functools + • itertools + • java + • os + • re + • socket + • struct + • subprocess + • sys + • vms_lib + • winreg + +
+
+imported by: + uuid + +
+ +
+ +
+ + posix (builtin module)
+imports: + resource + +
+
+imported by: + importlib._bootstrap_external + • os + • posixpath + • shutil + +
+ +
+ +
+ + posixpath +SourceModule
+imports: + genericpath + • os + • posix + • pwd + • re + • stat + • sys + +
+
+imported by: + fnmatch + • importlib.metadata + • mimetypes + • os + • os.path + • pathlib + • pointcab_renamer.py + • urllib.request + • zipfile + +
+ +
+ +
+ + pprint +SourceModule
+imports: + collections + • dataclasses + • io + • re + • sys + • time + • types + +
+
+imported by: + pickle + +
+ +
+ +
+ + pwd (builtin module)
+imported by: + getpass + • netrc + • pathlib + • posixpath + • shutil + • subprocess + • tarfile + +
+ +
+ +
+ + py_compile +SourceModule
+imports: + argparse + • enum + • importlib._bootstrap_external + • importlib.machinery + • importlib.util + • os + • os.path + • sys + • traceback + +
+
+imported by: + zipfile + +
+ +
+ +
+ + pyexpat /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/pyexpat.cpython-311-x86_64-linux-gnu.so
+imported by: + _elementtree + • xml.etree.ElementTree + • xml.parsers.expat + +
+ +
+ +
+ + quopri +SourceModule
+imports: + binascii + • getopt + • io + • sys + +
+
+imported by: + email.encoders + • email.message + • encodings.quopri_codec + +
+ +
+ +
+ + random +SourceModule
+imports: + _collections_abc + • _random + • _sha512 + • bisect + • hashlib + • itertools + • math + • operator + • os + • statistics + • time + • warnings + +
+
+imported by: + email.generator + • email.utils + • statistics + • tempfile + • uuid + +
+ +
+ +
+ + re +Package
+imports: + copyreg + • enum + • functools + • re + • re._compiler + • re._constants + • re._parser + • warnings + +
+ + +
+ +
+ + re._casefix +SourceModule
+imports: + re + +
+
+imported by: + pointcab_renamer.py + • re._compiler + +
+ +
+ +
+ + re._compiler +SourceModule
+imports: + _sre + • re + • re._casefix + • re._constants + • re._parser + • sys + +
+
+imported by: + pointcab_renamer.py + • re + • sre_compile + +
+ +
+ +
+ + re._constants +SourceModule
+imports: + _sre + • re + +
+
+imported by: + pointcab_renamer.py + • re + • re._compiler + • re._parser + • sre_constants + +
+ +
+ +
+ + re._parser +SourceModule
+imports: + re + • re._constants + • unicodedata + • warnings + +
+
+imported by: + pointcab_renamer.py + • re + • re._compiler + • sre_parse + +
+ +
+ +
+ + reprlib +SourceModule
+imports: + _thread + • builtins + • itertools + +
+
+imported by: + collections + • functools + • pointcab_renamer.py + +
+ +
+ +
+ + resource /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/resource.cpython-311-x86_64-linux-gnu.so
+imported by: + posix + +
+ +
+ +
+ + select /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/select.cpython-311-x86_64-linux-gnu.so
+imported by: + selectors + • subprocess + +
+ +
+ +
+ + selectors +SourceModule
+imports: + abc + • collections + • collections.abc + • math + • select + • sys + +
+
+imported by: + socket + • subprocess + +
+ +
+ +
+ + shlex +SourceModule
+imports: + collections + • io + • os + • re + • sys + • warnings + +
+
+imported by: + netrc + +
+ +
+ +
+ + shutil +SourceModule
+imports: + bz2 + • collections + • errno + • fnmatch + • grp + • lzma + • nt + • os + • posix + • pwd + • stat + • sys + • tarfile + • zipfile + • zlib + +
+
+imported by: + argparse + • pointcab_renamer.py + • tarfile + • tempfile + • uuid + • zipfile + +
+ +
+ +
+ + signal +SourceModule
+imports: + _signal + • enum + +
+
+imported by: + subprocess + +
+ +
+ +
+ + socket +SourceModule
+imports: + _socket + • array + • enum + • errno + • io + • os + • selectors + • sys + +
+
+imported by: + _ssl + • email.utils + • ftplib + • http.client + • platform + • ssl + • urllib.request + • uuid + +
+ +
+ +
+ + sre_compile +SourceModule
+imports: + re + • re._compiler + • warnings + +
+
+imported by: + pointcab_renamer.py + +
+ +
+ +
+ + sre_constants +SourceModule
+imports: + re + • re._constants + • warnings + +
+
+imported by: + pointcab_renamer.py + +
+ +
+ +
+ + sre_parse +SourceModule
+imports: + re + • re._parser + • warnings + +
+
+imported by: + pointcab_renamer.py + +
+ +
+ +
+ + ssl +SourceModule
+imports: + _ssl + • base64 + • calendar + • collections + • enum + • errno + • os + • socket + • sys + • time + • warnings + +
+
+imported by: + ftplib + • http.client + • urllib.request + +
+ +
+ +
+ + stat +SourceModule
+imports: + _stat + +
+
+imported by: + genericpath + • netrc + • ntpath + • os + • pathlib + • pointcab_renamer.py + • posixpath + • shutil + • tarfile + • zipfile + +
+ +
+ +
+ + statistics +SourceModule
+imports: + _statistics + • bisect + • collections + • decimal + • fractions + • functools + • itertools + • math + • numbers + • operator + • random + • sys + +
+
+imported by: + random + +
+ +
+ +
+ + string +SourceModule
+imports: + _string + • collections + • re + +
+ + +
+ +
+ + stringprep +SourceModule
+imports: + unicodedata + +
+
+imported by: + encodings.idna + +
+ +
+ +
+ + struct +SourceModule
+imports: + _struct + +
+
+imported by: + base64 + • gettext + • gzip + • pickle + • platform + • tarfile + • zipfile + +
+ +
+ +
+ + subprocess +SourceModule
+imports: + _posixsubprocess + • _winapi + • builtins + • contextlib + • errno + • fcntl + • grp + • io + • locale + • msvcrt + • os + • pwd + • select + • selectors + • signal + • sys + • threading + • time + • types + • warnings + +
+
+imported by: + os + • platform + • uuid + +
+ +
+ +
+ + sys (builtin module)
+imported by: + _collections_abc + • _compression + • _pydecimal + • argparse + • ast + • base64 + • calendar + • codecs + • collections + • contextlib + • dataclasses + • datetime + • dis + • email._header_value_parser + • email.generator + • email.iterators + • email.policy + • encodings + • encodings.rot_13 + • encodings.utf_16 + • encodings.utf_32 + • enum + • fractions + • ftplib + • getopt + • getpass + • gettext + • gzip + • http.client + • importlib + • importlib._bootstrap_external + • importlib.metadata + • importlib.util + • inspect + • linecache + • locale + • logging + • mimetypes + • ntpath + • os + • pathlib + • pickle + • platform + • posixpath + • pprint + • py_compile + • pyi_rth__tkinter.py + • pyi_rth_inspect.py + • quopri + • re._compiler + • selectors + • shlex + • shutil + • socket + • ssl + • statistics + • subprocess + • tarfile + • tempfile + • threading + • tkinter + • tkinter.filedialog + • tokenize + • traceback + • types + • typing + • urllib.parse + • urllib.request + • uuid + • warnings + • weakref + • xml.etree.ElementTree + • xml.parsers.expat + • xml.sax + • xml.sax._exceptions + • xml.sax.expatreader + • xml.sax.saxutils + • zipfile + +
+ +
+ +
+ + tarfile +SourceModule
+imports: + argparse + • builtins + • bz2 + • copy + • grp + • gzip + • io + • lzma + • os + • pwd + • re + • shutil + • stat + • struct + • sys + • time + • warnings + • zlib + +
+
+imported by: + shutil + +
+ +
+ +
+ + tempfile +SourceModule
+imports: + _thread + • errno + • functools + • io + • os + • random + • shutil + • sys + • types + • warnings + • weakref + +
+ + +
+ +
+ + termios /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/termios.cpython-311-x86_64-linux-gnu.so
+imported by: + getpass + +
+ +
+ +
+ + textwrap +SourceModule
+imports: + re + +
+
+imported by: + argparse + • importlib.metadata + • importlib.metadata._adapters + • traceback + +
+ +
+ +
+ + threading +SourceModule
+imports: + _collections + • _thread + • _threading_local + • _weakrefset + • collections + • functools + • itertools + • os + • sys + • time + • traceback + • warnings + +
+
+imported by: + _threading_local + • http.cookiejar + • logging + • subprocess + • zipfile + +
+ +
+ +
+ + time (builtin module)
+imports: + _strptime + +
+
+imported by: + _datetime + • _strptime + • datetime + • email._parseaddr + • email.generator + • email.utils + • gc + • gzip + • http.cookiejar + • logging + • pprint + • random + • ssl + • subprocess + • tarfile + • threading + • urllib.request + • uuid + • zipfile + +
+ +
+ + + +
+ + tkinter.commondialog +SourceModule
+imports: + tkinter + +
+
+imported by: + tkinter + • tkinter.filedialog + • tkinter.messagebox + +
+ +
+ +
+ + tkinter.constants +SourceModule
+imports: + tkinter + +
+
+imported by: + tkinter + • tkinter.scrolledtext + +
+ +
+ +
+ + tkinter.dialog +SourceModule
+imports: + tkinter + +
+
+imported by: + tkinter.filedialog + +
+ +
+ +
+ + tkinter.filedialog +SourceModule
+imports: + fnmatch + • locale + • os + • sys + • tkinter + • tkinter.commondialog + • tkinter.dialog + • tkinter.simpledialog + +
+
+imported by: + pointcab_renamer.py + • tkinter + +
+ +
+ +
+ + tkinter.messagebox +SourceModule
+imports: + tkinter + • tkinter.commondialog + +
+
+imported by: + pointcab_renamer.py + • tkinter + • tkinter.simpledialog + +
+ +
+ +
+ + tkinter.scrolledtext +SourceModule
+imports: + tkinter + • tkinter.constants + +
+
+imported by: + pointcab_renamer.py + • tkinter + +
+ +
+ +
+ + tkinter.simpledialog +SourceModule
+imports: + tkinter + • tkinter.messagebox + +
+
+imported by: + tkinter.filedialog + +
+ +
+ +
+ + tkinter.ttk +SourceModule
+imports: + tkinter + +
+
+imported by: + pointcab_renamer.py + • tkinter + +
+ +
+ +
+ + token +SourceModule
+imported by: + inspect + • tokenize + +
+ +
+ +
+ + tokenize +SourceModule
+imports: + _tokenize + • argparse + • builtins + • codecs + • collections + • functools + • io + • itertools + • re + • sys + • token + +
+
+imported by: + importlib._bootstrap_external + • inspect + • linecache + +
+ +
+ +
+ + traceback +SourceModule
+imports: + ast + • collections.abc + • contextlib + • itertools + • linecache + • sys + • textwrap + +
+
+imported by: + http.cookiejar + • logging + • pointcab_renamer.py + • py_compile + • threading + • tkinter + • warnings + +
+ +
+ +
+ + tracemalloc +SourceModule
+imports: + _tracemalloc + • collections.abc + • fnmatch + • functools + • linecache + • os.path + • pickle + +
+
+imported by: + warnings + +
+ +
+ +
+ + types +SourceModule
+imports: + _collections_abc + • functools + • sys + +
+ + +
+ +
+ + typing +SourceModule
+imports: + _typing + • abc + • collections + • collections.abc + • contextlib + • functools + • operator + • re + • sys + • types + • warnings + +
+ + +
+ +
+ + unicodedata /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/unicodedata.cpython-311-x86_64-linux-gnu.so
+imported by: + encodings.idna + • re._parser + • stringprep + • urllib.parse + +
+ +
+ +
+ + urllib +Package + +
+ +
+ + urllib.error +SourceModule
+imports: + io + • urllib + • urllib.response + +
+
+imported by: + urllib.request + +
+ +
+ +
+ + urllib.parse +SourceModule
+imports: + collections + • functools + • ipaddress + • re + • sys + • types + • unicodedata + • urllib + • warnings + +
+
+imported by: + email.utils + • http.client + • http.cookiejar + • mimetypes + • nturl2path + • pathlib + • urllib.request + • xml.etree.ElementInclude + • xml.sax.saxutils + +
+ +
+ +
+ + urllib.request +SourceModule
+imports: + _scproxy + • base64 + • bisect + • contextlib + • email + • email.utils + • fnmatch + • ftplib + • getpass + • hashlib + • http.client + • http.cookiejar + • io + • mimetypes + • nturl2path + • os + • posixpath + • re + • socket + • ssl + • string + • sys + • tempfile + • time + • urllib + • urllib.error + • urllib.parse + • urllib.response + • warnings + • winreg + +
+
+imported by: + http.cookiejar + • xml.sax.saxutils + +
+ +
+ +
+ + urllib.response +SourceModule
+imports: + tempfile + • urllib + +
+
+imported by: + urllib.error + • urllib.request + +
+ +
+ +
+ + uuid +SourceModule
+imports: + _uuid + • enum + • hashlib + • io + • os + • platform + • random + • shutil + • socket + • subprocess + • sys + • time + +
+
+imported by: + pointcab_renamer.py + +
+ +
+ +
+ + vms_lib +MissingModule
+imported by: + platform + +
+ +
+ +
+ + warnings +SourceModule
+imports: + _warnings + • builtins + • linecache + • re + • sys + • traceback + • tracemalloc + +
+
+imported by: + argparse + • ast + • enum + • ftplib + • getpass + • gettext + • gzip + • hashlib + • http.client + • http.cookiejar + • importlib + • importlib._abc + • importlib.abc + • importlib.metadata + • importlib.resources._legacy + • importlib.util + • io + • locale + • logging + • os + • pathlib + • pointcab_renamer.py + • random + • re + • re._parser + • shlex + • sre_compile + • sre_constants + • sre_parse + • ssl + • subprocess + • tarfile + • tempfile + • threading + • typing + • urllib.parse + • urllib.request + • xml.etree.ElementTree + • zipfile + +
+ +
+ +
+ + weakref +SourceModule
+imports: + _collections_abc + • _weakref + • _weakrefset + • atexit + • copy + • gc + • itertools + • sys + +
+
+imported by: + _threading_local + • copy + • functools + • logging + • pointcab_renamer.py + • tempfile + • xml.sax.expatreader + +
+ +
+ +
+ + winreg +MissingModule
+imported by: + importlib._bootstrap_external + • mimetypes + • platform + • urllib.request + +
+ +
+ +
+ + xml +Package
+imports: + xml.sax.expatreader + • xml.sax.xmlreader + +
+
+imported by: + xml.etree + • xml.parsers + • xml.sax + +
+ +
+ +
+ + xml.etree +Package
+imports: + xml + • xml.etree + • xml.etree.ElementPath + • xml.etree.ElementTree + +
+ + +
+ +
+ + xml.etree.ElementInclude +SourceModule
+imports: + copy + • urllib.parse + • xml.etree + • xml.etree.ElementTree + +
+
+imported by: + _elementtree + +
+ +
+ +
+ + xml.etree.ElementPath +SourceModule
+imports: + re + • xml.etree + +
+
+imported by: + _elementtree + • xml.etree + • xml.etree.ElementTree + +
+ +
+ +
+ + xml.etree.ElementTree +SourceModule
+imports: + _elementtree + • collections + • collections.abc + • contextlib + • io + • pyexpat + • re + • sys + • warnings + • xml.etree + • xml.etree.ElementPath + • xml.parsers + • xml.parsers.expat + +
+ + +
+ +
+ + xml.etree.cElementTree +SourceModule
+imports: + xml.etree + • xml.etree.ElementTree + +
+
+imported by: + _elementtree + +
+ +
+ +
+ + xml.parsers +Package
+imports: + xml + • xml.parsers.expat + +
+ + +
+ +
+ + xml.parsers.expat +SourceModule
+imports: + pyexpat + • sys + • xml.parsers + +
+
+imported by: + xml.etree.ElementTree + • xml.parsers + • xml.sax.expatreader + +
+ +
+ +
+ + xml.sax +Package
+imports: + 'org.python' + • io + • os + • sys + • xml + • xml.sax + • xml.sax._exceptions + • xml.sax.expatreader + • xml.sax.handler + • xml.sax.saxutils + • xml.sax.xmlreader + +
+ + +
+ +
+ + xml.sax._exceptions +SourceModule
+imports: + 'java.lang' + • sys + • xml.sax + +
+
+imported by: + xml.sax + • xml.sax.expatreader + • xml.sax.xmlreader + +
+ +
+ +
+ + xml.sax.expatreader +SourceModule
+imports: + _weakref + • sys + • weakref + • xml.parsers + • xml.parsers.expat + • xml.sax + • xml.sax._exceptions + • xml.sax.handler + • xml.sax.saxutils + • xml.sax.xmlreader + +
+
+imported by: + xml + • xml.sax + +
+ +
+ +
+ + xml.sax.handler +SourceModule
+imports: + xml.sax + +
+
+imported by: + xml.sax + • xml.sax.expatreader + • xml.sax.saxutils + • xml.sax.xmlreader + +
+ +
+ +
+ + xml.sax.saxutils +SourceModule
+imports: + codecs + • io + • os + • sys + • urllib.parse + • urllib.request + • xml.sax + • xml.sax.handler + • xml.sax.xmlreader + +
+
+imported by: + xml.sax + • xml.sax.expatreader + • xml.sax.xmlreader + +
+ +
+ +
+ + xml.sax.xmlreader +SourceModule
+imports: + xml.sax + • xml.sax._exceptions + • xml.sax.handler + • xml.sax.saxutils + +
+
+imported by: + xml + • xml.sax + • xml.sax.expatreader + • xml.sax.saxutils + +
+ +
+ +
+ + zipfile +SourceModule
+imports: + argparse + • binascii + • bz2 + • contextlib + • importlib.util + • io + • itertools + • lzma + • os + • pathlib + • posixpath + • py_compile + • shutil + • stat + • struct + • sys + • threading + • time + • warnings + • zlib + +
+ + +
+ +
+ + zlib /opt/computersetup/.pyenv/versions/3.11.6/lib/python3.11/lib-dynload/zlib.cpython-311-x86_64-linux-gnu.so
+imported by: + encodings.zlib_codec + • gzip + • shutil + • tarfile + • zipfile + +
+ +
+ + + diff --git a/build_linux.sh b/build_linux.sh new file mode 100755 index 0000000..e227275 --- /dev/null +++ b/build_linux.sh @@ -0,0 +1,160 @@ +#!/bin/bash +# ============================================ +# PointCab Renamer - Linux Build Script +# Version 4.2.1 +# ============================================ + +set -e # Bei Fehlern abbrechen + +echo "" +echo "===================================" +echo " PointCab Renamer - Linux Build" +echo " Version 4.2.1" +echo "===================================" +echo "" + +# Farben für Ausgabe +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Arbeitsverzeichnis +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo -e "${BLUE}[1/6] Prüfe Voraussetzungen...${NC}" + +# Prüfe ob Python3 installiert ist +if ! command -v python3 &> /dev/null; then + echo -e "${RED}[FEHLER] Python3 ist nicht installiert.${NC}" + echo "" + echo "Installation:" + echo " Ubuntu/Debian: sudo apt install python3 python3-pip python3-tk" + echo " Fedora: sudo dnf install python3 python3-pip python3-tkinter" + exit 1 +fi + +PYTHON_VERSION=$(python3 --version 2>&1) +echo -e "${GREEN}✓ Python3 gefunden: $PYTHON_VERSION${NC}" + +# Prüfe ob tkinter installiert ist +echo -e "${BLUE}[2/6] Prüfe tkinter...${NC}" +if ! python3 -c "import tkinter" 2>/dev/null; then + echo -e "${YELLOW}[WARNUNG] tkinter nicht gefunden.${NC}" + echo "" + echo "Installation von tkinter:" + echo " Ubuntu/Debian: sudo apt install python3-tk" + echo " Fedora: sudo dnf install python3-tkinter" + echo "" + + # Versuche automatische Installation (nur wenn sudo verfügbar) + if command -v apt &> /dev/null && [ -w /etc/apt ]; then + echo "Versuche automatische Installation..." + sudo apt install -y python3-tk || { + echo -e "${RED}[FEHLER] Automatische Installation fehlgeschlagen.${NC}" + echo "Bitte manuell installieren: sudo apt install python3-tk" + exit 1 + } + else + echo -e "${RED}[FEHLER] tkinter muss manuell installiert werden.${NC}" + exit 1 + fi +fi +echo -e "${GREEN}✓ tkinter verfügbar${NC}" + +# Prüfe ob pip installiert ist +echo -e "${BLUE}[3/6] Prüfe pip...${NC}" +if ! command -v pip3 &> /dev/null && ! python3 -m pip --version &> /dev/null; then + echo -e "${YELLOW}[INFO] pip3 nicht gefunden.${NC}" + echo "" + echo "Installation von pip:" + echo " Ubuntu/Debian: sudo apt install python3-pip" + echo " Fedora: sudo dnf install python3-pip" + echo " Oder: python3 -m ensurepip --upgrade" + exit 1 +fi +echo -e "${GREEN}✓ pip verfügbar${NC}" + +# Prüfe ob PyInstaller installiert ist +echo -e "${BLUE}[4/6] Prüfe PyInstaller...${NC}" +if ! python3 -m PyInstaller --version &> /dev/null 2>&1; then + echo -e "${YELLOW}[INFO] PyInstaller nicht gefunden. Installiere...${NC}" + python3 -m pip install --user pyinstaller || { + echo -e "${RED}[FEHLER] PyInstaller konnte nicht installiert werden.${NC}" + echo "Versuche: pip3 install pyinstaller" + exit 1 + } +fi +PYINSTALLER_VERSION=$(python3 -m PyInstaller --version 2>&1) +echo -e "${GREEN}✓ PyInstaller installiert: $PYINSTALLER_VERSION${NC}" + +# Prüfe Projektdateien +echo -e "${BLUE}[5/6] Prüfe Projektdateien...${NC}" +if [ ! -f "pointcab_renamer.py" ]; then + echo -e "${RED}[FEHLER] pointcab_renamer.py nicht gefunden!${NC}" + echo "Bitte führen Sie das Skript im Projektverzeichnis aus." + exit 1 +fi + +if [ ! -f "cluster_cleanup.txt" ]; then + echo -e "${RED}[FEHLER] cluster_cleanup.txt nicht gefunden!${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Projektdateien vorhanden${NC}" + +# Lösche alte Build-Verzeichnisse +echo "" +echo -e "${BLUE}[6/6] Starte Build-Prozess...${NC}" +echo "[INFO] Räume alte Build-Dateien auf..." +rm -rf build dist *.spec 2>/dev/null || true + +echo "[INFO] Erstelle Linux-Binary..." +echo "" + +# PyInstaller ausführen +python3 -m PyInstaller \ + --onefile \ + --name "pointcab_renamer" \ + --add-data "cluster_cleanup.txt:." \ + pointcab_renamer.py + +if [ $? -ne 0 ]; then + echo "" + echo -e "${RED}[FEHLER] Build fehlgeschlagen!${NC}" + echo "" + echo "Mögliche Ursachen:" + echo " - PyInstaller-Version inkompatibel" + echo " - Fehlende Abhängigkeiten" + echo "" + echo "Versuche: pip3 install --upgrade pyinstaller" + exit 1 +fi + +echo "" +echo -e "${GREEN}[INFO] Build erfolgreich!${NC}" +echo "" + +# Kopiere notwendige Dateien in dist-Ordner +echo "[INFO] Kopiere zusätzliche Dateien..." +cp cluster_cleanup.txt dist/ +[ -f BENUTZERHANDBUCH.md ] && cp BENUTZERHANDBUCH.md dist/ + +# Mache das Binary ausführbar +chmod +x dist/pointcab_renamer + +# Zeige Ergebnis +echo "" +echo -e "${GREEN}===================================${NC}" +echo -e "${GREEN} BUILD ERFOLGREICH!${NC}" +echo -e "${GREEN}===================================${NC}" +echo "" +echo "Erstellte Dateien:" +ls -lh dist/ +echo "" +echo "Verwendung:" +echo " cd dist && ./pointcab_renamer" +echo "" +echo "Für Distribution alle Dateien aus dist/ kopieren." +echo "" diff --git a/build_windows.bat b/build_windows.bat new file mode 100644 index 0000000..05139b6 --- /dev/null +++ b/build_windows.bat @@ -0,0 +1,119 @@ +@echo off +REM ============================================ +REM PointCab Renamer - Windows Build Script v4.2 +REM ============================================ +REM Verwendet den Python Launcher (py) für bessere Kompatibilität +REM +REM Voraussetzungen: +REM - Python 3.8+ mit py Launcher installiert +REM - PyInstaller (wird bei Bedarf installiert) +REM +REM Verwendung: +REM 1. Öffnen Sie die Eingabeaufforderung (cmd) +REM 2. Navigieren Sie zum Projektordner +REM 3. Führen Sie: build_windows.bat aus +REM ============================================ + +setlocal enabledelayedexpansion + +echo. +echo ============================================ +echo PointCab Renamer - Windows Build v4.2 +echo ============================================ +echo. + +REM Prüfe Python Installation +echo [1/5] Prüfe Python Installation... +py --version >nul 2>&1 +if errorlevel 1 ( + echo. + echo FEHLER: Python wurde nicht gefunden! + echo. + echo Bitte installieren Sie Python von: + echo https://www.python.org/downloads/ + echo. + echo Stellen Sie sicher, dass bei der Installation + echo "Add Python to PATH" aktiviert ist. + echo. + pause + exit /b 1 +) + +for /f "tokens=2" %%v in ('py --version 2^>^&1') do set PYTHON_VERSION=%%v +echo Python %PYTHON_VERSION% gefunden. + +REM Prüfe/Installiere PyInstaller +echo. +echo [2/5] Prüfe PyInstaller... +py -m PyInstaller --version >nul 2>&1 +if errorlevel 1 ( + echo PyInstaller nicht gefunden. Installiere... + py -m pip install pyinstaller + if errorlevel 1 ( + echo. + echo FEHLER: PyInstaller konnte nicht installiert werden! + echo Bitte führen Sie manuell aus: + echo py -m pip install pyinstaller + echo. + pause + exit /b 1 + ) +) +for /f "tokens=*" %%v in ('py -m PyInstaller --version 2^>^&1') do set PYINSTALLER_VERSION=%%v +echo PyInstaller %PYINSTALLER_VERSION% gefunden. + +REM Bereinige alte Builds +echo. +echo [3/5] Bereinige alte Build-Dateien... +if exist build rmdir /s /q build +if exist dist rmdir /s /q dist +if exist *.spec del /f /q *.spec +echo Alte Dateien entfernt. + +REM Erstelle Executable +echo. +echo [4/5] Erstelle Windows Executable... +echo Dies kann einige Minuten dauern... +echo. + +py -m PyInstaller --onefile --windowed --name "PointCab_Renamer" ^ + --add-data "cluster_cleanup.txt;." ^ + --add-data "BENUTZERHANDBUCH.md;." ^ + pointcab_renamer.py + +if errorlevel 1 ( + echo. + echo FEHLER: Build fehlgeschlagen! + echo Bitte prüfen Sie die Fehlermeldungen oben. + echo. + pause + exit /b 1 +) + +REM Kopiere zusätzliche Dateien +echo. +echo [5/5] Kopiere zusätzliche Dateien... +copy cluster_cleanup.txt dist\ >nul 2>&1 +copy BENUTZERHANDBUCH.md dist\ >nul 2>&1 +copy README.md dist\ >nul 2>&1 +echo Dateien kopiert. + +REM Erfolgsmeldung +echo. +echo ============================================ +echo BUILD ERFOLGREICH! +echo ============================================ +echo. +echo Die Executable befindet sich in: +echo dist\PointCab_Renamer.exe +echo. +echo Zusätzliche Dateien in dist\: +echo - cluster_cleanup.txt +echo - BENUTZERHANDBUCH.md +echo - README.md +echo. +echo Hinweis: Die cluster_cleanup.txt muss neben +echo der .exe Datei liegen! +echo. +echo ============================================ +pause diff --git a/build_windows_on_linux.sh b/build_windows_on_linux.sh new file mode 100755 index 0000000..1e93536 --- /dev/null +++ b/build_windows_on_linux.sh @@ -0,0 +1,278 @@ +#!/bin/bash +# ============================================================================ +# PointCab Renamer - Windows Build unter Linux (Docker-Methode) +# Version: 4.1.2 +# ============================================================================ +# +# Dieses Skript erstellt eine Windows .exe unter Linux mittels Docker. +# Es verwendet das cdrx/pyinstaller-windows Image für zuverlässige Builds. +# +# VORAUSSETZUNGEN: +# - Docker muss installiert sein (wird bei Bedarf mit sudo gestartet) +# - Internet-Verbindung für den ersten Docker-Image-Download +# +# VERWENDUNG: +# ./build_windows_on_linux.sh +# +# TROUBLESHOOTING: +# Falls Docker nicht startet, prüfen Sie: +# - sudo systemctl start docker +# - Benutzer zur docker-Gruppe hinzufügen: sudo usermod -aG docker $USER +# - In Container-Umgebungen (z.B. Docker-in-Docker): --privileged Flag nötig +# +# ============================================================================ + +set -e # Bei Fehlern abbrechen + +# Farben für Ausgabe +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Banner +echo -e "${BLUE}" +echo "============================================================================" +echo " PointCab Renamer - Windows Cross-Compilation unter Linux" +echo " Version 4.1.2" +echo "============================================================================" +echo -e "${NC}" + +# Hilfsfunktion: Docker-Befehl mit oder ohne sudo +docker_cmd() { + if docker "$@" 2>/dev/null; then + return 0 + elif sudo docker "$@" 2>/dev/null; then + USE_SUDO=1 + return 0 + else + return 1 + fi +} + +run_docker() { + if [ "$USE_SUDO" = "1" ]; then + sudo docker "$@" + else + docker "$@" + fi +} + +# [1/7] Prüfe ob Docker installiert ist +echo -e "${YELLOW}[1/7] Prüfe Docker-Installation...${NC}" +if ! command -v docker &> /dev/null; then + echo -e "${RED}FEHLER: Docker ist nicht installiert!${NC}" + echo "" + echo "Installiere Docker mit:" + echo " Ubuntu/Debian:" + echo " sudo apt update" + echo " sudo apt install docker.io" + echo " sudo systemctl start docker" + echo " sudo systemctl enable docker" + echo " sudo usermod -aG docker \$USER" + echo " # Danach neu einloggen oder: newgrp docker" + echo "" + echo " Fedora/RHEL:" + echo " sudo dnf install docker" + echo " sudo systemctl start docker" + echo "" + echo "Alternativ: Verwende ./build_windows_wine.sh (Wine-basiert)" + exit 1 +fi +echo -e "${GREEN}✓ Docker ist installiert${NC}" + +# [2/7] Prüfe ob Docker-Daemon läuft, versuche Start mit sudo falls nötig +echo -e "${YELLOW}[2/7] Prüfe Docker-Daemon...${NC}" +USE_SUDO=0 + +if docker info &> /dev/null; then + echo -e "${GREEN}✓ Docker-Daemon läuft (ohne sudo)${NC}" +elif sudo docker info &> /dev/null; then + USE_SUDO=1 + echo -e "${GREEN}✓ Docker-Daemon läuft (mit sudo)${NC}" +else + # Versuche Docker zu starten + echo -e "${YELLOW}Docker-Daemon läuft nicht. Versuche zu starten...${NC}" + + # Versuche systemd + if command -v systemctl &> /dev/null; then + sudo systemctl start docker 2>/dev/null && sleep 2 + fi + + # Prüfe erneut + if docker info &> /dev/null; then + echo -e "${GREEN}✓ Docker-Daemon erfolgreich gestartet${NC}" + elif sudo docker info &> /dev/null; then + USE_SUDO=1 + echo -e "${GREEN}✓ Docker-Daemon erfolgreich gestartet (mit sudo)${NC}" + else + echo -e "${RED}FEHLER: Docker-Daemon konnte nicht gestartet werden!${NC}" + echo "" + echo "Mögliche Ursachen und Lösungen:" + echo "" + echo "1. Docker-Service nicht gestartet:" + echo " sudo systemctl start docker" + echo "" + echo "2. Berechtigungsproblem:" + echo " sudo usermod -aG docker \$USER" + echo " # Dann neu einloggen" + echo "" + echo "3. In Container-Umgebung (Docker-in-Docker):" + echo " Docker kann nicht in unprivilegierten Containern laufen." + echo " Nutze stattdessen: ./build_windows_wine.sh" + echo " Oder: Baue auf einem System mit nativem Docker." + echo "" + echo "4. WSL2 unter Windows:" + echo " Starte Docker Desktop und aktiviere WSL2-Integration." + echo "" + echo "Alternative Build-Methoden:" + echo " - ./build_windows_wine.sh (Wine-basiert)" + echo " - GitHub Actions (siehe .github/workflows/)" + echo " - Natives Windows: build_windows.bat" + exit 1 + fi +fi + +# Arbeitsverzeichnis +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# [3/7] Prüfe ob notwendige Dateien existieren +echo -e "${YELLOW}[3/7] Prüfe Projektdateien...${NC}" +if [ ! -f "pointcab_renamer.py" ]; then + echo -e "${RED}FEHLER: pointcab_renamer.py nicht gefunden!${NC}" + echo "Stelle sicher, dass du dich im richtigen Verzeichnis befindest." + exit 1 +fi + +if [ ! -f "cluster_cleanup.txt" ]; then + echo -e "${RED}FEHLER: cluster_cleanup.txt nicht gefunden!${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Alle Projektdateien vorhanden${NC}" + +# [4/7] Aufräumen +echo -e "${YELLOW}[4/7] Räume alte Build-Artefakte auf...${NC}" +rm -rf build/ dist/ *.spec 2>/dev/null || true +mkdir -p dist +echo -e "${GREEN}✓ Build-Verzeichnisse bereinigt${NC}" + +# Docker Image +DOCKER_IMAGE="cdrx/pyinstaller-windows:python3" + +# [5/7] Prüfe/Lade Docker Image +echo -e "${YELLOW}[5/7] Prüfe Docker Image...${NC}" +echo " Image: $DOCKER_IMAGE" +echo " (Beim ersten Mal wird ~1.5 GB heruntergeladen)" + +if ! run_docker image inspect "$DOCKER_IMAGE" &> /dev/null; then + echo "" + echo "Lade Docker Image herunter..." + run_docker pull "$DOCKER_IMAGE" +fi +echo -e "${GREEN}✓ Docker Image bereit${NC}" + +# [6/7] Build durchführen +echo -e "${YELLOW}[6/7] Erstelle Windows .exe mit PyInstaller...${NC}" +echo " Dies kann 2-5 Minuten dauern..." +echo "" + +# Erstelle temporäres Build-Skript für Docker-Container +BUILD_SCRIPT=$(mktemp) +cat > "$BUILD_SCRIPT" << 'DOCKERSCRIPT' +#!/bin/bash +set -e +cd /src + +echo "=== Docker Container gestartet ===" +echo "Python-Version: $(python --version)" +echo "PyInstaller-Version: $(pip show pyinstaller | grep Version)" +echo "" + +# Installiere requirements falls vorhanden +if [ -f "requirements.txt" ]; then + echo "Installiere requirements..." + pip install -r requirements.txt --quiet +fi + +echo "Starte PyInstaller Build..." + +# PyInstaller Build +pyinstaller --onefile \ + --windowed \ + --name "PointCab_Renamer" \ + --add-data "cluster_cleanup.txt;." \ + --add-data "BENUTZERHANDBUCH.md;." \ + --icon="NONE" \ + --clean \ + pointcab_renamer.py + +echo "" +echo "=== Build im Container abgeschlossen ===" +DOCKERSCRIPT + +chmod +x "$BUILD_SCRIPT" + +# Docker Container ausführen +if [ "$USE_SUDO" = "1" ]; then + sudo docker run --rm \ + -v "$SCRIPT_DIR":/src \ + -v "$BUILD_SCRIPT":/docker_build.sh:ro \ + "$DOCKER_IMAGE" \ + bash /docker_build.sh +else + docker run --rm \ + -v "$SCRIPT_DIR":/src \ + -v "$BUILD_SCRIPT":/docker_build.sh:ro \ + "$DOCKER_IMAGE" \ + bash /docker_build.sh +fi + +# Aufräumen +rm -f "$BUILD_SCRIPT" + +# [7/7] Prüfe Ergebnis +echo "" +echo -e "${YELLOW}[7/7] Verifiziere Build-Ergebnis...${NC}" + +if [ -f "dist/PointCab_Renamer.exe" ]; then + echo -e "${GREEN}✓ Windows .exe erfolgreich erstellt!${NC}" + + # Kopiere zusätzliche Dateien + cp cluster_cleanup.txt dist/ + [ -f "BENUTZERHANDBUCH.md" ] && cp BENUTZERHANDBUCH.md dist/ + [ -f "BENUTZERHANDBUCH.pdf" ] && cp BENUTZERHANDBUCH.pdf dist/ + [ -f "README.md" ] && cp README.md dist/ + [ -f "LICENSE.txt" ] && cp LICENSE.txt dist/ + + # Zeige Ergebnis + echo "" + echo -e "${BLUE}============================================================================${NC}" + echo -e "${GREEN}BUILD ERFOLGREICH!${NC}" + echo -e "${BLUE}============================================================================${NC}" + echo "" + echo "Erstellte Dateien in dist/:" + ls -lh dist/ + echo "" + EXE_SIZE=$(du -h "dist/PointCab_Renamer.exe" | cut -f1) + echo "Executable-Größe: $EXE_SIZE" + echo "" + echo "Pfad: $SCRIPT_DIR/dist/" + echo "" + echo -e "${YELLOW}WICHTIG:${NC}" + echo " 1. Teste die .exe auf einem echten Windows-System!" + echo " 2. Stelle sicher, dass cluster_cleanup.txt im gleichen" + echo " Ordner wie die .exe liegt." + echo "" +else + echo -e "${RED}FEHLER: Build fehlgeschlagen!${NC}" + echo "" + echo "Die Datei dist/PointCab_Renamer.exe wurde nicht erstellt." + echo "" + echo "Prüfe die Fehlermeldungen oben und versuche:" + echo " 1. ./build_windows_wine.sh (Wine-Alternative)" + echo " 2. Build auf nativem Windows mit build_windows.bat" + echo "" + exit 1 +fi diff --git a/build_windows_wine.sh b/build_windows_wine.sh new file mode 100755 index 0000000..26479fa --- /dev/null +++ b/build_windows_wine.sh @@ -0,0 +1,274 @@ +#!/bin/bash +# ============================================================================ +# PointCab Renamer - Windows Build unter Linux (Wine-Methode) +# Version: 4.2.1 +# ============================================================================ +# +# WICHTIG: Wine-basierte Builds sind EXPERIMENTELL und können fehlschlagen! +# +# BEKANNTE EINSCHRÄNKUNGEN: +# - Headless Server (ohne GUI): Python-Installation kann fehlschlagen +# - Manche Wine-Versionen haben Kompatibilitätsprobleme +# - GUI-basierte Python-Installer benötigen X11/Display +# +# EMPFOHLENE ALTERNATIVEN: +# 1. Windows-PC: build_windows.bat direkt ausführen +# 2. GitHub Actions: CI/CD für automatisierte Builds +# 3. Dual-Boot/VM: Windows-Build in echter Windows-Umgebung +# +# ============================================================================ + +# Farben für Ausgabe +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Konfiguration +PYTHON_VERSION="3.10.11" +PYTHON_INSTALLER="python-${PYTHON_VERSION}-amd64.exe" +PYTHON_URL="https://www.python.org/ftp/python/${PYTHON_VERSION}/${PYTHON_INSTALLER}" +WINE_PREFIX="$HOME/.wine_python" + +# Banner +echo -e "${BLUE}" +echo "============================================================================" +echo " PointCab Renamer - Windows Cross-Compilation (Wine-Methode)" +echo " Version 4.2.1 - EXPERIMENTELL" +echo "============================================================================" +echo -e "${NC}" + +echo -e "${YELLOW}╔════════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${YELLOW}║ WARNUNG: Diese Methode ist EXPERIMENTELL und kann fehlschlagen! ║${NC}" +echo -e "${YELLOW}╚════════════════════════════════════════════════════════════════════╝${NC}" +echo "" +echo "Bekannte Probleme:" +echo " • Headless Server ohne X11: Python-Installer kann hängen" +echo " • Wine-Kompatibilität variiert je nach Version" +echo " • ~500MB Download und 10+ Minuten Installationszeit" +echo "" +echo "Empfohlene Alternativen:" +echo " 1. Windows-PC: build_windows.bat ausführen" +echo " 2. GitHub Actions: Automatisierte CI/CD Builds" +echo "" +read -p "Trotzdem fortfahren? (j/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Jj]$ ]]; then + echo "" + echo "Abgebrochen. Alternative Methoden:" + echo " • Kopiere das Projekt auf einen Windows-PC" + echo " • Führe dort build_windows.bat aus" + exit 0 +fi +echo "" + +# Arbeitsverzeichnis +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Funktion für Fehlerbehandlung +fail_with_alternatives() { + echo "" + echo -e "${RED}╔════════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${RED}║ BUILD FEHLGESCHLAGEN ║${NC}" + echo -e "${RED}╚════════════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo -e "${YELLOW}Alternative Methoden:${NC}" + echo "" + echo " 1. ${GREEN}Windows-PC (EMPFOHLEN):${NC}" + echo " - Kopiere das gesamte Projektverzeichnis" + echo " - Führe build_windows.bat aus" + echo "" + echo " 2. ${GREEN}GitHub Actions:${NC}" + echo " - Push zu GitHub mit Actions Workflow" + echo " - Automatisierter Windows-Build" + echo "" + echo " 3. ${GREEN}Virtual Machine:${NC}" + echo " - Windows-VM mit VirtualBox/VMware" + echo " - Shared Folder für Projektdateien" + echo "" + exit 1 +} + +# Prüfe Wine +echo -e "${BLUE}[1/7] Prüfe Wine-Installation...${NC}" +if ! command -v wine &> /dev/null; then + echo -e "${RED}FEHLER: Wine ist nicht installiert!${NC}" + echo "" + echo "Installation auf Ubuntu/Debian:" + echo " sudo dpkg --add-architecture i386" + echo " sudo apt update" + echo " sudo apt install wine64 wine32" + echo "" + echo "Installation auf Fedora:" + echo " sudo dnf install wine" + echo "" + fail_with_alternatives +fi + +WINE_VERSION=$(wine --version 2>/dev/null || echo "unknown") +echo -e "${GREEN}✓ Wine installiert: $WINE_VERSION${NC}" + +# Prüfe auf Display (wichtig für GUI-Installer) +echo -e "${BLUE}[2/7] Prüfe Display-Umgebung...${NC}" +if [ -z "$DISPLAY" ]; then + echo -e "${YELLOW}⚠ Kein DISPLAY gesetzt - Headless-Modus erkannt${NC}" + echo "" + echo "Python-Installer benötigt möglicherweise X11/GUI." + echo "Dies kann in Headless-Umgebungen fehlschlagen." + echo "" + echo "Optionen:" + echo " • Virtuelles Display mit Xvfb (experimentell)" + echo " • X11 Forwarding bei SSH (-X Option)" + echo " • Desktop-Umgebung verwenden" + echo "" + + # Versuche Xvfb falls verfügbar + if command -v Xvfb &> /dev/null; then + echo "Xvfb gefunden - starte virtuelles Display..." + Xvfb :99 -screen 0 1024x768x24 & + XVFB_PID=$! + export DISPLAY=:99 + sleep 2 + echo -e "${GREEN}✓ Virtuelles Display gestartet (:99)${NC}" + else + echo -e "${YELLOW}Xvfb nicht installiert. Versuche ohne Display...${NC}" + echo " Installation: sudo apt install xvfb" + fi +else + echo -e "${GREEN}✓ Display vorhanden: $DISPLAY${NC}" +fi + +# Prüfe Projektdateien +echo -e "${BLUE}[3/7] Prüfe Projektdateien...${NC}" +if [ ! -f "pointcab_renamer.py" ]; then + echo -e "${RED}FEHLER: pointcab_renamer.py nicht gefunden!${NC}" + exit 1 +fi +if [ ! -f "cluster_cleanup.txt" ]; then + echo -e "${RED}FEHLER: cluster_cleanup.txt nicht gefunden!${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Projektdateien vorhanden${NC}" + +# Wine-Prefix initialisieren +echo -e "${BLUE}[4/7] Initialisiere Wine-Umgebung...${NC}" +export WINEPREFIX="$WINE_PREFIX" +export WINEARCH=win64 +export WINEDEBUG=-all # Unterdrücke Wine Debug-Meldungen + +if [ ! -d "$WINE_PREFIX" ]; then + echo "Erstelle Wine-Prefix unter: $WINE_PREFIX" + echo "Dies kann einige Minuten dauern..." + wineboot --init 2>/dev/null || true + sleep 5 +fi +echo -e "${GREEN}✓ Wine-Umgebung bereit${NC}" + +# Python-Pfade in Wine +WINE_PYTHON="$WINE_PREFIX/drive_c/Python310/python.exe" +WINE_PIP="$WINE_PREFIX/drive_c/Python310/Scripts/pip.exe" +WINE_PYINSTALLER="$WINE_PREFIX/drive_c/Python310/Scripts/pyinstaller.exe" + +# Python installieren falls nicht vorhanden +echo -e "${BLUE}[5/7] Prüfe Windows-Python...${NC}" +if [ ! -f "$WINE_PYTHON" ]; then + echo "Windows-Python nicht gefunden, installiere..." + echo "" + + # Download Python installer + if [ ! -f "/tmp/$PYTHON_INSTALLER" ]; then + echo "Lade Python herunter: $PYTHON_URL" + echo "Größe: ~28MB" + wget -q --show-progress -O "/tmp/$PYTHON_INSTALLER" "$PYTHON_URL" || { + echo -e "${RED}FEHLER: Python-Download fehlgeschlagen!${NC}" + fail_with_alternatives + } + fi + + # Installiere Python + echo "" + echo "Installiere Python in Wine..." + echo "Dies kann 5-10 Minuten dauern..." + echo "" + + # Silent Installation mit Timeout + timeout 300 wine "/tmp/$PYTHON_INSTALLER" /quiet InstallAllUsers=0 PrependPath=1 Include_test=0 2>/dev/null || { + echo -e "${YELLOW}⚠ Python-Installation möglicherweise fehlgeschlagen oder Timeout${NC}" + } + + sleep 5 + + # Prüfe Installation + if [ ! -f "$WINE_PYTHON" ]; then + echo "" + echo -e "${RED}FEHLER: Python-Installation fehlgeschlagen!${NC}" + echo "" + echo "Mögliche Ursachen:" + echo " • Headless-Umgebung ohne GUI" + echo " • Wine-Kompatibilitätsproblem" + echo " • Nicht genug Speicherplatz" + echo "" + + # Cleanup Xvfb falls gestartet + [ -n "$XVFB_PID" ] && kill $XVFB_PID 2>/dev/null + + fail_with_alternatives + fi +fi +echo -e "${GREEN}✓ Windows-Python installiert${NC}" + +# PyInstaller installieren +echo -e "${BLUE}[6/7] Prüfe PyInstaller...${NC}" +if [ ! -f "$WINE_PYINSTALLER" ]; then + echo "Installiere PyInstaller..." + wine "$WINE_PIP" install pyinstaller 2>/dev/null || { + echo -e "${RED}FEHLER: PyInstaller-Installation fehlgeschlagen!${NC}" + fail_with_alternatives + } +fi +echo -e "${GREEN}✓ PyInstaller installiert${NC}" + +# Aufräumen +echo -e "${BLUE}[7/7] Erstelle Windows .exe...${NC}" +rm -rf build/ dist/ *.spec 2>/dev/null || true + +echo "Build läuft... (kann einige Minuten dauern)" +echo "" + +# Führe PyInstaller aus +wine "$WINE_PYINSTALLER" \ + --onefile \ + --windowed \ + --name "PointCab_Renamer" \ + --add-data "cluster_cleanup.txt;." \ + pointcab_renamer.py 2>&1 | grep -v "^fixme:" | grep -v "^err:" || true + +# Cleanup Xvfb falls gestartet +[ -n "$XVFB_PID" ] && kill $XVFB_PID 2>/dev/null + +# Prüfe Ergebnis +echo "" +if [ -f "dist/PointCab_Renamer.exe" ]; then + echo -e "${GREEN}╔════════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ BUILD ERFOLGREICH! ║${NC}" + echo -e "${GREEN}╚════════════════════════════════════════════════════════════════════╝${NC}" + + # Kopiere zusätzliche Dateien + cp cluster_cleanup.txt dist/ + cp BENUTZERHANDBUCH.md dist/ 2>/dev/null || true + + echo "" + echo "Erstellte Dateien:" + ls -lh dist/ + echo "" + echo "Die .exe befindet sich in: $SCRIPT_DIR/dist/" + echo "" + echo -e "${YELLOW}WICHTIG:${NC}" + echo " Die .exe sollte auf einem echten Windows-System" + echo " getestet werden, bevor sie verteilt wird!" + echo "" +else + fail_with_alternatives +fi diff --git a/cluster_cleanup.txt b/cluster_cleanup.txt new file mode 100644 index 0000000..4a7f921 --- /dev/null +++ b/cluster_cleanup.txt @@ -0,0 +1,16 @@ +# Konfigurationsdatei für Clustername-Bereinigung +# Jede Zeile enthält einen String, der aus dem Stammverzeichnisnamen entfernt wird +# Leerzeilen und Zeilen die mit # beginnen werden ignoriert +# +# Beispiel: Stammverzeichnis "Am_Upstall_4_re" wird zu Clustername "Am_Upstall_4" +# +_re +_li +_mi +_mi-li +_mi-re +_part_1 +_part_2 +_part_3 +_part_4 +_part_5 diff --git a/dist/BENUTZERHANDBUCH.md b/dist/BENUTZERHANDBUCH.md new file mode 100644 index 0000000..053639e --- /dev/null +++ b/dist/BENUTZERHANDBUCH.md @@ -0,0 +1,385 @@ +# PointCab Renamer - Benutzerhandbuch + +**Version 4.1** | Datum: 14. Januar 2026 + +--- + +## Inhaltsverzeichnis + +1. [Einführung](#einführung) +2. [Installation](#installation) +3. [Programmstart](#programmstart) +4. [Die drei Modi](#die-drei-modi) + - [Einzelprojekt-Modus](#einzelprojekt-modus) + - [Batch-Modus](#batch-modus) + - [Projekt-Merger](#projekt-merger) +5. [Konfiguration](#konfiguration) +6. [Troubleshooting](#troubleshooting) +7. [FAQ](#faq) + +--- + +## Einführung + +### Was ist der PointCab Renamer? + +Der **PointCab Renamer** ist ein Werkzeug zur automatischen Umbenennung von PointCab-Projektdateien. Es löst das Problem, dass PointCab-Scandateien oft kryptische Namen haben (z.B. `1.lsd`, `2.lsd`) und benennt diese nach einem einheitlichen Schema um: + +**Format:** `[ClusterName]_[ScanName].[Erweiterung]` + +**Beispiel:** `EG_Flur_scan001.lsd` + +### Hauptfunktionen + +- **Einzelprojekt-Modus**: Ein einzelnes PointCab-Projekt umbenennen +- **Batch-Modus**: Mehrere Projekte gleichzeitig verarbeiten +- **Projekt-Merger**: Mehrere Projekte in ein Zielprojekt zusammenführen +- **Cluster-Bereinigung**: Automatische Entfernung von Suffixen wie `_re`, `_li` aus Clusternamen +- **Detailliertes Logging**: Vollständige Protokollierung aller Änderungen + +--- + +## Installation + +### Windows + +1. Laden Sie die Datei `pointcab_renamer.exe` herunter +2. Speichern Sie die Datei in einem beliebigen Ordner (z.B. `C:\Tools\`) +3. Kopieren Sie die `cluster_cleanup.txt` in denselben Ordner +4. Starten Sie das Programm mit Doppelklick auf die `.exe` + +### Ubuntu/Linux + +1. Laden Sie die Datei `pointcab_renamer` herunter +2. Speichern Sie die Datei in einem beliebigen Ordner (z.B. `/home/benutzer/tools/`) +3. Kopieren Sie die `cluster_cleanup.txt` in denselben Ordner +4. Machen Sie die Datei ausführbar: + ```bash + chmod +x pointcab_renamer + ``` +5. Starten Sie das Programm: + ```bash + ./pointcab_renamer + ``` + +### Aus dem Quellcode (für Entwickler) + +1. Stellen Sie sicher, dass Python 3.8+ installiert ist +2. Laden Sie den Quellcode herunter +3. Starten Sie mit: + ```bash + python pointcab_renamer.py + ``` + +--- + +## Programmstart + +### Hauptmenü + +Nach dem Start erscheint das Hauptmenü mit drei Optionen: + +``` +╔═══════════════════════════════════════════╗ +║ PointCab Renamer v4.1 ║ +╠═══════════════════════════════════════════╣ +║ ║ +║ [Einzelprojekt umbenennen] ║ +║ ║ +║ [Batch-Verarbeitung] ║ +║ ║ +║ [Projekt Merger] ║ +║ ║ +╚═══════════════════════════════════════════╝ +``` + +--- + +## Die drei Modi + +### Einzelprojekt-Modus + +**Verwendung:** Wenn Sie ein einzelnes PointCab-Projekt umbenennen möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Einzelprojekt umbenennen"** +2. Wählen Sie die **LSDX-Projektdatei** aus (z.B. `Am_Upstall_4.lsdx`) +3. Wählen Sie den **PointCloud-Ordner** aus (enthält die `.lsd` Dateien) +4. Das Programm zeigt eine **Vorschau** der Änderungen: + ``` + Vorschau der Umbenennung: + ───────────────────────── + 1.lsd → EG_Flur_scan001.lsd + 2.lsd → EG_Flur_scan002.lsd + 3.lsd → OG_Bad_scan001.lsd + ... + ``` +5. Klicken Sie auf **"Umbenennen starten"** +6. Nach Abschluss wird ein Protokoll angezeigt + +#### Dateistruktur (Vorher → Nachher) + +**Vorher:** +``` +Am_Upstall_4_PointCloud/ +├── 1.lsd +├── 2.lsd +├── 3.lsd +└── ... +``` + +**Nachher:** +``` +Am_Upstall_4_PointCloud/ +├── EG_Flur_scan001.lsd +├── EG_Flur_scan002.lsd +├── OG_Bad_scan001.lsd +└── ... +``` + +--- + +### Batch-Modus + +**Verwendung:** Wenn Sie mehrere PointCab-Projekte auf einmal verarbeiten möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Batch-Verarbeitung"** +2. Wählen Sie den **Basisordner** aus, der alle Projekte enthält +3. Das Programm erkennt automatisch alle PointCab-Projekte: + ``` + Gefundene Projekte: + ───────────────────── + ☑ Projekt_A (15 Scans) + ☑ Projekt_B (22 Scans) + ☑ Projekt_C (8 Scans) + ``` +4. Wählen Sie die gewünschten Projekte aus (oder behalten Sie alle ausgewählt) +5. Klicken Sie auf **"Batch-Verarbeitung starten"** +6. Der Fortschritt wird angezeigt: + ``` + Verarbeite Projekt 1/3: Projekt_A + [████████████░░░░░░░░] 60% + ``` +7. Nach Abschluss wird eine Zusammenfassung angezeigt + +#### Erwartete Ordnerstruktur + +``` +Basisordner/ +├── Projekt_A/ +│ ├── Projekt_A.lsdx +│ └── Projekt_A_PointCloud/ +│ ├── 1.lsd +│ └── ... +├── Projekt_B/ +│ ├── Projekt_B.lsdx +│ └── Projekt_B_PointCloud/ +└── Projekt_C/ + ├── Projekt_C.lsdx + └── Projekt_C_PointCloud/ +``` + +--- + +### Projekt-Merger + +**Verwendung:** Wenn Sie mehrere PointCab-Projekte in ein einziges Projekt zusammenführen möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Projekt Merger"** +2. Wählen Sie den **Modus**: + - **Einzelprojekt zusammenführen**: Ein Quellprojekt → Zielprojekt + - **Batch-Merge**: Mehrere Quellprojekte → Zielprojekt +3. Wählen Sie das **Zielprojekt** (in das zusammengeführt wird) +4. Wählen Sie das/die **Quellprojekt(e)** +5. Das Programm zeigt eine **Vorschau** mit Konfliktauflösung: + ``` + Merge-Vorschau: + ───────────────────── + Zielprojekt: Haupt_Projekt (5 Cluster, 25 Scans) + Quellprojekt: Teil_A (2 Cluster, 10 Scans) + + Zu übertragende Dateien: + - EG_Flur_scan001.lsd + - EG_Flur_scan002.lsd (Konflikt → EG_Flur_scan002_merged_1.lsd) + - ... + ``` +6. Klicken Sie auf **"Merge starten"** +7. Nach Abschluss werden die Statistiken angezeigt: + ``` + Merge abgeschlossen! + ───────────────────── + Cluster vorher: 5 → nachher: 7 + Scans vorher: 25 → nachher: 35 + Dateien kopiert: 10 + Konflikte gelöst: 1 + ``` + +#### Konfliktauflösung + +Wenn eine Datei im Zielprojekt bereits existiert: +- Die neue Datei wird umbenannt: `dateiname_merged_1.lsd` +- Bei weiteren Konflikten: `dateiname_merged_2.lsd`, etc. +- Die LSDX-Datei wird entsprechend aktualisiert + +#### Wichtige Hinweise + +- **Backup**: Das Zielprojekt wird vor dem Merge gesichert (`.lsdx.backup`) +- **UUID-Regenerierung**: Alle übertragenen Elemente erhalten neue eindeutige IDs +- **Cluster-Duplikate**: Gleichnamige Cluster werden zusammengeführt + +--- + +## Konfiguration + +### Die Datei cluster_cleanup.txt + +Diese Konfigurationsdatei definiert, welche Suffixe aus Clusternamen entfernt werden sollen. + +#### Speicherort + +- **Windows**: Im selben Ordner wie `pointcab_renamer.exe` +- **Linux**: Im selben Ordner wie `pointcab_renamer` + +#### Format + +``` +# Dies ist ein Kommentar +_re +_li +_mi +_mi-li +_mi-re +``` + +- Jede Zeile = ein zu entfernender String +- Zeilen mit `#` am Anfang sind Kommentare +- Leere Zeilen werden ignoriert + +#### Beispiel + +Mit der obigen Konfiguration: +- `Flur_re` → `Flur` +- `Bad_mi-li` → `Bad` +- `Küche_li` → `Küche` + +#### Konfiguration neu laden + +Änderungen an `cluster_cleanup.txt` werden nach einem Neustart oder über den Button **"Konfiguration neu laden"** übernommen. + +--- + +## Troubleshooting + +### Häufige Fehler und Lösungen + +#### "LSDX-Datei nicht gefunden" + +**Problem**: Das Programm kann die Projektdatei nicht finden. + +**Lösung**: +- Stellen Sie sicher, dass die `.lsdx`-Datei im Projektordner existiert +- Prüfen Sie, ob Sie Leserechte für die Datei haben +- Der Dateiname sollte mit `.lsdx` enden (nicht `.LSDX`) + +#### "PointCloud-Ordner nicht gefunden" + +**Problem**: Der Ordner mit den Scandaten fehlt. + +**Lösung**: +- Der Ordner muss `_PointCloud` im Namen haben +- Beispiel: `Projekt_A_PointCloud` +- Prüfen Sie die Ordnerstruktur + +#### "Keine Projekte gefunden" (Batch-Modus) + +**Problem**: Im Basisordner werden keine Projekte erkannt. + +**Lösung**: +- Jedes Projekt muss eine `.lsdx`-Datei und einen `_PointCloud`-Ordner haben +- Der Projektname in der LSDX muss mit dem Ordnernamen übereinstimmen + +#### "Zugriff verweigert" + +**Problem**: Dateien können nicht umbenannt werden. + +**Lösung**: +- Schließen Sie PointCab +- Prüfen Sie, ob andere Programme die Dateien verwenden +- Unter Windows: Als Administrator ausführen +- Unter Linux: Prüfen Sie die Dateiberechtigungen + +#### "GUI startet nicht" (Linux) + +**Problem**: Keine grafische Oberfläche unter Linux. + +**Lösung**: +- Installieren Sie tkinter: `sudo apt install python3-tk` +- Stellen Sie sicher, dass ein Display verfügbar ist + +--- + +## FAQ + +### Allgemeine Fragen + +**F: Werden die Originaldateien gelöscht?** + +A: Nein, die Dateien werden nur umbenannt. Es werden keine Daten gelöscht. + +**F: Kann ich die Umbenennung rückgängig machen?** + +A: Die ursprünglichen Namen werden im Log-Datei protokolliert. Eine automatische Rückgängig-Funktion gibt es nicht. + +**F: Funktioniert das Tool auch mit älteren PointCab-Versionen?** + +A: Das Tool wurde für PointCab-Projekte mit LSDX-Format entwickelt. Ältere Formate werden möglicherweise nicht unterstützt. + +**F: Wie viele Projekte kann ich im Batch-Modus verarbeiten?** + +A: Es gibt keine feste Grenze. Die Verarbeitungszeit hängt von der Anzahl der Scans ab. + +### Technische Fragen + +**F: Wo werden die Log-Dateien gespeichert?** + +A: Im Projektordner, mit dem Format: +- Einzelprojekt: `rename_YYYYMMDD_HHMMSS.log` +- Batch: `batch_YYYYMMDD_HHMMSS.log` +- Merger: `merge_YYYYMMDD_HHMMSS.log` + +**F: Was passiert bei einem Stromausfall während der Verarbeitung?** + +A: Die bereits umbenannten Dateien bleiben umbenannt. Die LSDX-Datei wird erst nach erfolgreicher Umbenennung aktualisiert. + +**F: Kann ich das Tool über die Kommandozeile nutzen?** + +A: Aktuell nur mit grafischer Oberfläche. Kommandozeilen-Unterstützung ist für eine zukünftige Version geplant. + +### Merger-Fragen + +**F: Was passiert mit den Originalprojekten beim Merge?** + +A: Die Quellprojekte werden nicht verändert. Dateien werden kopiert, nicht verschoben. + +**F: Kann ich den Merge rückgängig machen?** + +A: Die ursprüngliche LSDX wird als `.backup` gespeichert. Die kopierten Dateien müssen manuell gelöscht werden. + +**F: Werden alle Unterordner (Previews, etc.) auch gemergt?** + +A: Ja, alle relevanten Unterordner werden übertragen. + +--- + +## Support + +Bei Fragen oder Problemen wenden Sie sich an Ihren Administrator. + +--- + +*PointCab Renamer v4.1 - © 2026* diff --git a/dist/cluster_cleanup.txt b/dist/cluster_cleanup.txt new file mode 100644 index 0000000..4a7f921 --- /dev/null +++ b/dist/cluster_cleanup.txt @@ -0,0 +1,16 @@ +# Konfigurationsdatei für Clustername-Bereinigung +# Jede Zeile enthält einen String, der aus dem Stammverzeichnisnamen entfernt wird +# Leerzeilen und Zeilen die mit # beginnen werden ignoriert +# +# Beispiel: Stammverzeichnis "Am_Upstall_4_re" wird zu Clustername "Am_Upstall_4" +# +_re +_li +_mi +_mi-li +_mi-re +_part_1 +_part_2 +_part_3 +_part_4 +_part_5 diff --git a/dist/pointcab_renamer b/dist/pointcab_renamer new file mode 100755 index 0000000..554c23d Binary files /dev/null and b/dist/pointcab_renamer differ diff --git a/pointcab_renamer.py b/pointcab_renamer.py new file mode 100644 index 0000000..60d3379 --- /dev/null +++ b/pointcab_renamer.py @@ -0,0 +1,2058 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +PointCab Project Renamer v4.2 +A GUI tool for renaming scans in PointCab projects with full scan names. +Now with Batch Renamer and Project Merger support! + +LSDX-Struktur: +- + - + - + - # Wurzelelement + - + - + - + - 1.lsd + - Previews/1.png + +Author: DeepAgent +Date: 2026-01-14 +""" + +import os +import re +import shutil +import logging +import tkinter as tk +from tkinter import ttk, filedialog, messagebox, scrolledtext +from datetime import datetime +import xml.etree.ElementTree as ET +from pathlib import Path +import uuid +import copy + + +class PointCabRenamer: + """Main application class for PointCab project renaming (Single Project Mode).""" + + def __init__(self, root, return_callback=None): + self.root = root + self.return_callback = return_callback + self.root.title("PointCab Projekt Umbenenner - Einzelprojekt") + self.root.geometry("850x750") + self.root.minsize(700, 600) + + # Variables + self.root_dir = tk.StringVar() + self.pointcloud_dir = None + self.lsdx_file = None + self.preview_dir = None + self.changes = [] + self.logger = None + self.cleanup_strings = [] + + # Load cleanup configuration + self.load_cleanup_config() + + self.setup_ui() + + def load_cleanup_config(self): + """Load cleanup strings from cluster_cleanup.txt.""" + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cluster_cleanup.txt") + self.cleanup_strings = [] + + if os.path.exists(config_path): + try: + # Use utf-8-sig to handle BOM from Windows editors + with open(config_path, 'r', encoding='utf-8-sig') as f: + for line in f: + # Strip whitespace including BOM characters + line = line.strip() + # Skip empty lines and comments + if not line: + continue + if line.startswith('#'): + continue + # Remove any remaining BOM or special chars + line = line.lstrip('\ufeff') + if line: + self.cleanup_strings.append(line) + print(f"Cleanup-Konfiguration geladen: {len(self.cleanup_strings)} Einträge") + except Exception as e: + print(f"Fehler beim Laden der Cleanup-Konfiguration: {e}") + + def clean_cluster_name(self, name): + """Remove cleanup strings from the cluster name.""" + cleaned_name = name + removed_strings = [] + + for cleanup_str in self.cleanup_strings: + if cleanup_str in cleaned_name: + removed_strings.append(cleanup_str) + cleaned_name = cleaned_name.replace(cleanup_str, '') + + return cleaned_name, removed_strings + + def setup_ui(self): + """Setup the user interface.""" + # Main frame with padding + main_frame = ttk.Frame(self.root, padding="10") + main_frame.pack(fill=tk.BOTH, expand=True) + + # Back button if we have a return callback + if self.return_callback: + back_frame = ttk.Frame(main_frame) + back_frame.pack(fill=tk.X, pady=(0, 10)) + ttk.Button(back_frame, text="← Zurück zum Hauptmenü", command=self.go_back).pack(side=tk.LEFT) + + # Directory selection frame + dir_frame = ttk.LabelFrame(main_frame, text="Projektverzeichnis", padding="5") + dir_frame.pack(fill=tk.X, pady=(0, 10)) + + ttk.Entry(dir_frame, textvariable=self.root_dir, width=60).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) + ttk.Button(dir_frame, text="Durchsuchen...", command=self.select_directory).pack(side=tk.LEFT) + ttk.Button(dir_frame, text="Analysieren", command=self.analyze_project).pack(side=tk.LEFT, padx=(5, 0)) + + # Info frame + self.info_frame = ttk.LabelFrame(main_frame, text="Projektinformationen", padding="5") + self.info_frame.pack(fill=tk.X, pady=(0, 10)) + + self.info_label = ttk.Label(self.info_frame, text="Bitte wählen Sie ein Projektverzeichnis aus.", wraplength=800) + self.info_label.pack(fill=tk.X) + + # Cleanup config info frame + cleanup_frame = ttk.LabelFrame(main_frame, text="Clustername-Bereinigung", padding="5") + cleanup_frame.pack(fill=tk.X, pady=(0, 10)) + + cleanup_info = f"Strings die aus dem Clusternamen entfernt werden: {', '.join(self.cleanup_strings) if self.cleanup_strings else '(keine)'}\n" + cleanup_info += f"📁 Konfigurationsdatei: cluster_cleanup.txt (im Programmverzeichnis)" + self.cleanup_label = ttk.Label(cleanup_frame, text=cleanup_info, wraplength=800, foreground="gray") + self.cleanup_label.pack(fill=tk.X) + + ttk.Button(cleanup_frame, text="Konfiguration neu laden", command=self.reload_cleanup_config).pack(side=tk.LEFT, pady=(5, 0)) + + # Changes preview frame + preview_frame = ttk.LabelFrame(main_frame, text="Vorschau der Änderungen", padding="5") + preview_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + self.preview_text = scrolledtext.ScrolledText(preview_frame, wrap=tk.WORD, height=20, font=('Consolas', 9)) + self.preview_text.pack(fill=tk.BOTH, expand=True) + self.preview_text.config(state=tk.DISABLED) + + # Status frame + status_frame = ttk.Frame(main_frame) + status_frame.pack(fill=tk.X, pady=(0, 10)) + + self.status_label = ttk.Label(status_frame, text="Bereit", foreground="gray") + self.status_label.pack(side=tk.LEFT) + + self.progress = ttk.Progressbar(status_frame, mode='indeterminate', length=200) + self.progress.pack(side=tk.RIGHT) + + # Buttons frame + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=tk.X) + + self.execute_btn = ttk.Button(button_frame, text="Änderungen ausführen", command=self.execute_changes, state=tk.DISABLED) + self.execute_btn.pack(side=tk.RIGHT) + + ttk.Button(button_frame, text="Beenden", command=self.root.quit).pack(side=tk.LEFT) + + def go_back(self): + """Return to main menu.""" + if self.return_callback: + self.return_callback() + + def reload_cleanup_config(self): + """Reload cleanup configuration and update display.""" + self.load_cleanup_config() + cleanup_info = f"Strings die aus dem Clusternamen entfernt werden: {', '.join(self.cleanup_strings) if self.cleanup_strings else '(keine)'}\n" + cleanup_info += f"📁 Konfigurationsdatei: cluster_cleanup.txt (im Programmverzeichnis)" + self.cleanup_label.config(text=cleanup_info) + messagebox.showinfo("Info", f"Konfiguration neu geladen.\n{len(self.cleanup_strings)} Cleanup-Strings gefunden.") + + def select_directory(self): + """Open directory selection dialog.""" + directory = filedialog.askdirectory(title="Stammverzeichnis auswählen") + if directory: + self.root_dir.set(directory) + self.analyze_project() + + def setup_logging(self, log_dir): + """Setup logging to file.""" + log_file = os.path.join(log_dir, "renamer.log") + + # Create logger + self.logger = logging.getLogger('PointCabRenamer') + self.logger.setLevel(logging.INFO) + + # Clear existing handlers + self.logger.handlers.clear() + + # File handler + fh = logging.FileHandler(log_file, encoding='utf-8') + fh.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + fh.setFormatter(formatter) + self.logger.addHandler(fh) + + return log_file + + def find_pointcloud_folder(self, root_dir): + """Find the PointCloud folder in the root directory.""" + root_name = os.path.basename(root_dir) + expected_name = f"{root_name}_PointCloud" + + # Check for expected folder name + expected_path = os.path.join(root_dir, expected_name) + if os.path.isdir(expected_path): + return expected_path + + # Search for any folder ending with _PointCloud + for item in os.listdir(root_dir): + item_path = os.path.join(root_dir, item) + if os.path.isdir(item_path) and item.endswith("_PointCloud"): + return item_path + + return None + + def find_lsdx_file(self, pointcloud_dir): + """Find the LSDX file in the PointCloud directory.""" + for item in os.listdir(pointcloud_dir): + if item.endswith(".lsdx"): + return os.path.join(pointcloud_dir, item) + return None + + def get_file_mapping(self, file_list, extension, scan_prefix): + """Create mapping for files with full scan names.""" + # Extract numbers from filenames + files_with_nums = [] + for f in file_list: + match = re.match(r'^(\d+)\.' + extension + '$', f) + if match: + num = int(match.group(1)) + files_with_nums.append((num, f)) + + if not files_with_nums: + return {}, 2 + + files_with_nums.sort(key=lambda x: x[0]) + max_num = max(num for num, _ in files_with_nums) + + # Determine padding width + if max_num >= 100: + width = 3 + else: + width = 2 + + # Create mapping with full scan name + mapping = {} + for num, old_name in files_with_nums: + new_num_str = str(num).zfill(width) + new_name = f"{scan_prefix}_{new_num_str}.{extension}" + if old_name != new_name: + mapping[old_name] = new_name + + return mapping, width + + def get_scan_name_mappings(self, lsdx_file, width, scan_prefix): + """Get scan name mappings from LSDX file.""" + mappings = [] + try: + tree = ET.parse(lsdx_file) + root = tree.getroot() + + # Find all scan elements with name attribute + for element in root.findall(".//Element[@type='scan']"): + old_name = element.get('name') + if old_name: + # Try to extract number from name + match = re.match(r'^(\d+)$', old_name) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(width) + new_name = f"{scan_prefix}_{new_num_str}" + if old_name != new_name: + mappings.append((old_name, new_name)) + except Exception as e: + print(f"Fehler beim Lesen der Scan-Namen: {e}") + + return mappings + + def analyze_project(self): + """Analyze the selected project directory.""" + root_dir = self.root_dir.get() + + if not root_dir or not os.path.isdir(root_dir): + messagebox.showerror("Fehler", "Bitte wählen Sie ein gültiges Verzeichnis aus.") + return + + self.update_status("Analysiere Projekt...") + self.progress.start() + self.changes = [] + + try: + # Setup logging + log_file = self.setup_logging(root_dir) + self.logger.info(f"Analyse gestartet für: {root_dir}") + + # Find PointCloud folder + self.pointcloud_dir = self.find_pointcloud_folder(root_dir) + if not self.pointcloud_dir: + messagebox.showerror("Fehler", "PointCloud-Ordner nicht gefunden!") + self.progress.stop() + self.update_status("Fehler: PointCloud-Ordner nicht gefunden") + return + + # Find LSDX file + self.lsdx_file = self.find_lsdx_file(self.pointcloud_dir) + if not self.lsdx_file: + messagebox.showerror("Fehler", "LSDX-Datei nicht gefunden!") + self.progress.stop() + self.update_status("Fehler: LSDX-Datei nicht gefunden") + return + + # Find Previews folder + self.preview_dir = os.path.join(self.pointcloud_dir, "Previews") + if not os.path.isdir(self.preview_dir): + self.preview_dir = None + + # Get root name and clean cluster name + root_name = os.path.basename(root_dir) + cluster_name, removed_strings = self.clean_cluster_name(root_name) + scan_prefix = root_name + + # Update info + info_text = f"Stammverzeichnis: {root_name}\n" + info_text += f"PointCloud-Ordner: {os.path.basename(self.pointcloud_dir)}\n" + info_text += f"LSDX-Datei: {os.path.basename(self.lsdx_file)}\n" + info_text += f"Preview-Ordner: {'Gefunden' if self.preview_dir else 'Nicht gefunden'}\n" + info_text += f"Clustername: {root_name} → {cluster_name}" + if removed_strings: + info_text += f" (entfernt: {', '.join(removed_strings)})" + info_text += f"\nScan-Namen-Präfix: {scan_prefix} (unverändert)" + self.info_label.config(text=info_text) + + # Collect LSD files + lsd_files = [f for f in os.listdir(self.pointcloud_dir) if f.endswith('.lsd')] + + # Get mapping for LSD files + lsd_mapping, lsd_width = self.get_file_mapping(lsd_files, 'lsd', scan_prefix) + + # Collect PNG files if preview folder exists + png_mapping = {} + png_width = lsd_width + if self.preview_dir: + png_files = [f for f in os.listdir(self.preview_dir) if f.endswith('.png')] + png_mapping, png_width = self.get_file_mapping(png_files, 'png', scan_prefix) + + # Get scan name mappings + scan_name_mappings = self.get_scan_name_mappings(self.lsdx_file, lsd_width, scan_prefix) + + # Build changes list + preview_text = "=== GEPLANTE ÄNDERUNGEN ===\n\n" + + # LSDX Backup + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_name = f"{os.path.basename(self.lsdx_file)}.backup_{timestamp}" + preview_text += f"📁 BACKUP der LSDX-Datei:\n" + preview_text += f" → {backup_name}\n\n" + self.changes.append(('backup', self.lsdx_file, os.path.join(self.pointcloud_dir, backup_name))) + + # LSD file renames + if lsd_mapping: + preview_text += f"📄 LSD-Dateien umbenennen ({len(lsd_mapping)} Dateien):\n" + for old, new in sorted(lsd_mapping.items(), key=lambda x: x[0]): + preview_text += f" {old} → {new}\n" + self.changes.append(('rename_lsd', + os.path.join(self.pointcloud_dir, old), + os.path.join(self.pointcloud_dir, new))) + preview_text += "\n" + else: + preview_text += "📄 LSD-Dateien: Keine Umbenennung erforderlich\n\n" + + # PNG file renames + if self.preview_dir and png_mapping: + preview_text += f"🖼️ PNG-Dateien umbenennen ({len(png_mapping)} Dateien):\n" + for old, new in sorted(png_mapping.items(), key=lambda x: x[0]): + preview_text += f" {old} → {new}\n" + self.changes.append(('rename_png', + os.path.join(self.preview_dir, old), + os.path.join(self.preview_dir, new))) + preview_text += "\n" + elif self.preview_dir: + preview_text += "🖼️ PNG-Dateien: Keine Umbenennung erforderlich\n\n" + else: + preview_text += "🖼️ PNG-Dateien: Preview-Ordner nicht vorhanden\n\n" + + # Scan name changes + if scan_name_mappings: + preview_text += f"🏷️ Scan-Namen in LSDX aktualisieren ({len(scan_name_mappings)} Scans):\n" + for old_name, new_name in scan_name_mappings[:10]: + preview_text += f" name=\"{old_name}\" → name=\"{new_name}\"\n" + if len(scan_name_mappings) > 10: + preview_text += f" ... und {len(scan_name_mappings) - 10} weitere\n" + preview_text += "\n" + else: + preview_text += "🏷️ Scan-Namen: Keine Änderungen erforderlich\n\n" + + # LSDX updates + preview_text += "📝 LSDX-Datei aktualisieren:\n" + preview_text += f" - Clustername: '{root_name}' → '{cluster_name}'" + if removed_strings: + preview_text += f" (entfernt: {', '.join(removed_strings)})" + preview_text += "\n" + preview_text += f" - Dateinamen in FilePath-Elementen mit vollständigem Scan-Namen\n" + preview_text += f" - Beispiel: 1.lsd → {scan_prefix}_01.lsd\n" + preview_text += f" - Scan-Namen mit Präfix '{scan_prefix}_' versehen\n" + + self.changes.append(('update_lsdx', self.lsdx_file, { + 'cluster_name': cluster_name, + 'scan_prefix': scan_prefix, + 'original_name': root_name, + 'removed_strings': removed_strings, + 'lsd_width': lsd_width, + 'png_width': png_width if self.preview_dir else lsd_width + })) + + preview_text += f"\n=== ZUSAMMENFASSUNG ===\n" + preview_text += f"Gesamtänderungen: {len(self.changes)}\n" + if removed_strings: + preview_text += f"Bereinigte Strings: {', '.join(removed_strings)}\n" + preview_text += f"Log-Datei: {log_file}\n" + + # Update preview + self.preview_text.config(state=tk.NORMAL) + self.preview_text.delete(1.0, tk.END) + self.preview_text.insert(tk.END, preview_text) + self.preview_text.config(state=tk.DISABLED) + + # Enable execute button if there are changes + if self.changes: + self.execute_btn.config(state=tk.NORMAL) + + self.logger.info(f"Analyse abgeschlossen: {len(self.changes)} Änderungen geplant") + self.logger.info(f"Clustername: {root_name} → {cluster_name}") + self.logger.info(f"Scan-Namen-Präfix: {scan_prefix}") + if removed_strings: + self.logger.info(f"Entfernte Strings: {', '.join(removed_strings)}") + self.update_status(f"Analyse abgeschlossen: {len(self.changes)} Änderungen geplant") + + except Exception as e: + messagebox.showerror("Fehler", f"Fehler bei der Analyse:\n{str(e)}") + if self.logger: + self.logger.error(f"Analysefehler: {str(e)}") + self.update_status("Fehler bei der Analyse") + + finally: + self.progress.stop() + + def execute_changes(self): + """Execute all planned changes.""" + if not self.changes: + messagebox.showinfo("Info", "Keine Änderungen zum Ausführen.") + return + + # Confirmation dialog + result = messagebox.askyesno( + "Bestätigung", + f"Möchten Sie wirklich {len(self.changes)} Änderungen ausführen?\n\n" + "Ein Backup der LSDX-Datei wird automatisch erstellt." + ) + + if not result: + return + + self.update_status("Führe Änderungen aus...") + self.progress.start() + self.execute_btn.config(state=tk.DISABLED) + + try: + self.logger.info("Starte Ausführung der Änderungen") + + # First pass: backup and collect rename operations + renames_lsd = [] + renames_png = [] + lsdx_update = None + + for change in self.changes: + if change[0] == 'backup': + # Create backup + _, src, dst = change + shutil.copy2(src, dst) + self.logger.info(f"Backup erstellt: {dst}") + elif change[0] == 'rename_lsd': + renames_lsd.append((change[1], change[2])) + elif change[0] == 'rename_png': + renames_png.append((change[1], change[2])) + elif change[0] == 'update_lsdx': + lsdx_update = change + + # Execute renames with temporary names to avoid conflicts + def safe_rename(rename_list, file_type): + """Rename files using temporary names to avoid conflicts.""" + temp_suffix = f"_temp_{datetime.now().strftime('%Y%m%d%H%M%S')}" + + # First: rename to temporary names + temp_mappings = [] + for src, dst in rename_list: + if os.path.exists(src): + temp_name = src + temp_suffix + os.rename(src, temp_name) + temp_mappings.append((temp_name, dst)) + self.logger.info(f"{file_type}: {os.path.basename(src)} → temp") + + # Second: rename from temporary to final names + for temp_name, dst in temp_mappings: + os.rename(temp_name, dst) + self.logger.info(f"{file_type}: temp → {os.path.basename(dst)}") + + safe_rename(renames_lsd, "LSD") + safe_rename(renames_png, "PNG") + + # Update LSDX file + if lsdx_update: + self.update_lsdx_file(lsdx_update[1], lsdx_update[2]) + + self.logger.info("Alle Änderungen erfolgreich ausgeführt") + self.update_status("Alle Änderungen erfolgreich ausgeführt!") + messagebox.showinfo("Erfolg", "Alle Änderungen wurden erfolgreich ausgeführt!") + + # Clear changes + self.changes = [] + self.preview_text.config(state=tk.NORMAL) + self.preview_text.delete(1.0, tk.END) + self.preview_text.insert(tk.END, "Alle Änderungen wurden erfolgreich ausgeführt.\n\nSie können nun ein neues Projekt auswählen.") + self.preview_text.config(state=tk.DISABLED) + + except Exception as e: + messagebox.showerror("Fehler", f"Fehler bei der Ausführung:\n{str(e)}") + self.logger.error(f"Ausführungsfehler: {str(e)}") + self.update_status("Fehler bei der Ausführung") + self.execute_btn.config(state=tk.NORMAL) + + finally: + self.progress.stop() + + def update_lsdx_file(self, lsdx_path, params): + """Update the LSDX file with new filenames, cluster name, and scan names.""" + cluster_name = params['cluster_name'] + scan_prefix = params.get('scan_prefix', params.get('original_name', cluster_name)) + original_name = params.get('original_name', cluster_name) + removed_strings = params.get('removed_strings', []) + lsd_width = params['lsd_width'] + png_width = params['png_width'] + + # Parse XML + tree = ET.parse(lsdx_path) + root = tree.getroot() + + # Update cluster name + for element in root.findall(".//Element[@type='cluster']"): + old_cluster = element.get('name') + element.set('name', cluster_name) + self.logger.info(f"Clustername: '{old_cluster}' → '{cluster_name}'") + if removed_strings: + self.logger.info(f" Entfernte Strings: {', '.join(removed_strings)}") + + # Update scan names with prefix + for element in root.findall(".//Element[@type='scan']"): + old_name = element.get('name') + if old_name: + match = re.match(r'^(\d+)$', old_name) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(lsd_width) + new_name = f"{scan_prefix}_{new_num_str}" + element.set('name', new_name) + self.logger.info(f"Scan-Name: '{old_name}' → '{new_name}'") + + # Update FilePath elements + for filepath in root.findall(".//FilePath"): + file_type = filepath.get('type') + text = filepath.text + + if file_type == 'lsd' and text: + match = re.match(r'^(\d+)\.lsd$', text) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(lsd_width) + new_name = f"{scan_prefix}_{new_num_str}.lsd" + filepath.text = new_name + self.logger.info(f"LSDX FilePath: {text} → {new_name}") + + elif file_type == 'preview' and text: + match = re.match(r'^Previews/(\d+)\.png$', text) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(png_width) + new_name = f"Previews/{scan_prefix}_{new_num_str}.png" + filepath.text = new_name + self.logger.info(f"LSDX FilePath: {text} → {new_name}") + + # Write back + tree.write(lsdx_path, encoding='utf-8', xml_declaration=True) + + # Add DOCTYPE back + with open(lsdx_path, 'r', encoding='utf-8') as f: + content = f.read() + + if '' not in content: + content = content.replace( + "", + "\n" + ) + with open(lsdx_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.logger.info("LSDX-Datei erfolgreich aktualisiert") + + def update_status(self, message): + """Update the status label.""" + self.status_label.config(text=message) + self.root.update_idletasks() + + +class BatchRenamer: + """Batch Renamer for processing multiple PointCab projects.""" + + def __init__(self, root, return_callback=None): + self.root = root + self.return_callback = return_callback + self.root.title("PointCab Batch Renamer") + self.root.geometry("900x700") + self.root.minsize(800, 600) + + # Variables + self.main_dir = tk.StringVar() + self.projects = [] + self.cleanup_strings = [] + self.batch_logger = None + self.processing = False + + # Load cleanup configuration + self.load_cleanup_config() + + self.setup_ui() + + def load_cleanup_config(self): + """Load cleanup strings from cluster_cleanup.txt.""" + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cluster_cleanup.txt") + self.cleanup_strings = [] + + if os.path.exists(config_path): + try: + # Use utf-8-sig to handle BOM from Windows editors + with open(config_path, 'r', encoding='utf-8-sig') as f: + for line in f: + # Strip whitespace including BOM characters + line = line.strip() + # Skip empty lines and comments + if not line: + continue + if line.startswith('#'): + continue + # Remove any remaining BOM or special chars + line = line.lstrip('\ufeff') + if line: + self.cleanup_strings.append(line) + print(f"Cleanup-Konfiguration geladen: {len(self.cleanup_strings)} Einträge") + except Exception as e: + print(f"Fehler beim Laden der Cleanup-Konfiguration: {e}") + + def clean_cluster_name(self, name): + """Remove cleanup strings from the cluster name.""" + cleaned_name = name + removed_strings = [] + + for cleanup_str in self.cleanup_strings: + if cleanup_str in cleaned_name: + removed_strings.append(cleanup_str) + cleaned_name = cleaned_name.replace(cleanup_str, '') + + return cleaned_name, removed_strings + + def setup_ui(self): + """Setup the batch renamer UI.""" + main_frame = ttk.Frame(self.root, padding="10") + main_frame.pack(fill=tk.BOTH, expand=True) + + # Back button + if self.return_callback: + back_frame = ttk.Frame(main_frame) + back_frame.pack(fill=tk.X, pady=(0, 10)) + ttk.Button(back_frame, text="← Zurück zum Hauptmenü", command=self.go_back).pack(side=tk.LEFT) + + # Directory selection + dir_frame = ttk.LabelFrame(main_frame, text="Hauptverzeichnis auswählen", padding="5") + dir_frame.pack(fill=tk.X, pady=(0, 10)) + + ttk.Entry(dir_frame, textvariable=self.main_dir, width=60).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) + ttk.Button(dir_frame, text="Durchsuchen...", command=self.select_directory).pack(side=tk.LEFT) + ttk.Button(dir_frame, text="Projekte suchen", command=self.scan_for_projects).pack(side=tk.LEFT, padx=(5, 0)) + + # Projects list + projects_frame = ttk.LabelFrame(main_frame, text="Gefundene PointCab-Projekte", padding="5") + projects_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + # Treeview for projects + columns = ('status', 'project', 'cluster', 'scans') + self.projects_tree = ttk.Treeview(projects_frame, columns=columns, show='headings', height=10) + self.projects_tree.heading('status', text='Status') + self.projects_tree.heading('project', text='Projekt') + self.projects_tree.heading('cluster', text='Clustername') + self.projects_tree.heading('scans', text='Scans') + self.projects_tree.column('status', width=100) + self.projects_tree.column('project', width=250) + self.projects_tree.column('cluster', width=150) + self.projects_tree.column('scans', width=80) + + scrollbar = ttk.Scrollbar(projects_frame, orient=tk.VERTICAL, command=self.projects_tree.yview) + self.projects_tree.configure(yscrollcommand=scrollbar.set) + + self.projects_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # Info label + self.info_label = ttk.Label(main_frame, text="Wählen Sie ein Hauptverzeichnis und klicken Sie auf 'Projekte suchen'.") + self.info_label.pack(fill=tk.X, pady=(0, 10)) + + # Progress section + progress_frame = ttk.LabelFrame(main_frame, text="Fortschritt", padding="5") + progress_frame.pack(fill=tk.X, pady=(0, 10)) + + self.progress_var = tk.DoubleVar() + self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100) + self.progress_bar.pack(fill=tk.X, pady=(0, 5)) + + self.progress_label = ttk.Label(progress_frame, text="") + self.progress_label.pack(fill=tk.X) + + # Log area + log_frame = ttk.LabelFrame(main_frame, text="Verarbeitungslog", padding="5") + log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=10, font=('Consolas', 9)) + self.log_text.pack(fill=tk.BOTH, expand=True) + self.log_text.config(state=tk.DISABLED) + + # Buttons + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=tk.X) + + self.process_btn = ttk.Button(button_frame, text="Alle verarbeiten", command=self.process_all, state=tk.DISABLED) + self.process_btn.pack(side=tk.RIGHT) + + ttk.Button(button_frame, text="Beenden", command=self.root.quit).pack(side=tk.LEFT) + + def go_back(self): + """Return to main menu.""" + if self.processing: + messagebox.showwarning("Warnung", "Bitte warten Sie, bis die Verarbeitung abgeschlossen ist.") + return + if self.return_callback: + self.return_callback() + + def select_directory(self): + """Open directory selection dialog.""" + directory = filedialog.askdirectory(title="Hauptverzeichnis auswählen") + if directory: + self.main_dir.set(directory) + + def scan_for_projects(self): + """Scan the main directory for PointCab projects.""" + main_dir = self.main_dir.get() + + if not main_dir or not os.path.isdir(main_dir): + messagebox.showerror("Fehler", "Bitte wählen Sie ein gültiges Hauptverzeichnis aus.") + return + + self.projects = [] + self.projects_tree.delete(*self.projects_tree.get_children()) + + # Scan for subdirectories with _PointCloud folder + for item in os.listdir(main_dir): + item_path = os.path.join(main_dir, item) + if os.path.isdir(item_path): + pointcloud_folder = self.find_pointcloud_folder(item_path) + if pointcloud_folder: + lsdx_file = self.find_lsdx_file(pointcloud_folder) + if lsdx_file: + scan_count = self.count_lsd_files(pointcloud_folder) + cluster_name, _ = self.clean_cluster_name(item) + + self.projects.append({ + 'path': item_path, + 'name': item, + 'pointcloud': pointcloud_folder, + 'lsdx': lsdx_file, + 'cluster_name': cluster_name, + 'scan_count': scan_count, + 'status': 'Ausstehend' + }) + + self.projects_tree.insert('', tk.END, values=( + 'Ausstehend', item, cluster_name, scan_count + )) + + if self.projects: + self.info_label.config(text=f"{len(self.projects)} PointCab-Projekte gefunden.") + self.process_btn.config(state=tk.NORMAL) + else: + self.info_label.config(text="Keine PointCab-Projekte gefunden.") + self.process_btn.config(state=tk.DISABLED) + + def find_pointcloud_folder(self, root_dir): + """Find the PointCloud folder in the root directory.""" + root_name = os.path.basename(root_dir) + expected_name = f"{root_name}_PointCloud" + + expected_path = os.path.join(root_dir, expected_name) + if os.path.isdir(expected_path): + return expected_path + + for item in os.listdir(root_dir): + item_path = os.path.join(root_dir, item) + if os.path.isdir(item_path) and item.endswith("_PointCloud"): + return item_path + + return None + + def find_lsdx_file(self, pointcloud_dir): + """Find the LSDX file in the PointCloud directory.""" + for item in os.listdir(pointcloud_dir): + if item.endswith(".lsdx"): + return os.path.join(pointcloud_dir, item) + return None + + def count_lsd_files(self, pointcloud_dir): + """Count LSD files in directory.""" + return len([f for f in os.listdir(pointcloud_dir) if f.endswith('.lsd')]) + + def setup_batch_logging(self, main_dir): + """Setup batch summary logging.""" + log_file = os.path.join(main_dir, "batch_summary.log") + + self.batch_logger = logging.getLogger('BatchRenamer') + self.batch_logger.setLevel(logging.INFO) + self.batch_logger.handlers.clear() + + fh = logging.FileHandler(log_file, encoding='utf-8') + fh.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + fh.setFormatter(formatter) + self.batch_logger.addHandler(fh) + + return log_file + + def log_message(self, message, level='info'): + """Add message to log display and batch log.""" + self.log_text.config(state=tk.NORMAL) + timestamp = datetime.now().strftime("%H:%M:%S") + self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") + self.log_text.see(tk.END) + self.log_text.config(state=tk.DISABLED) + + if self.batch_logger: + if level == 'error': + self.batch_logger.error(message) + else: + self.batch_logger.info(message) + + self.root.update_idletasks() + + def update_project_status(self, index, status): + """Update project status in treeview.""" + item_id = self.projects_tree.get_children()[index] + values = list(self.projects_tree.item(item_id)['values']) + values[0] = status + self.projects_tree.item(item_id, values=values) + self.root.update_idletasks() + + def process_all(self): + """Process all found projects.""" + if not self.projects: + messagebox.showinfo("Info", "Keine Projekte zum Verarbeiten.") + return + + result = messagebox.askyesno( + "Bestätigung", + f"Möchten Sie wirklich {len(self.projects)} Projekte verarbeiten?\n\n" + "Ein Backup der LSDX-Dateien wird automatisch erstellt." + ) + + if not result: + return + + self.processing = True + self.process_btn.config(state=tk.DISABLED) + + # Clear log + self.log_text.config(state=tk.NORMAL) + self.log_text.delete(1.0, tk.END) + self.log_text.config(state=tk.DISABLED) + + # Setup batch logging + batch_log = self.setup_batch_logging(self.main_dir.get()) + + self.log_message(f"=== Batch-Verarbeitung gestartet ===") + self.log_message(f"Hauptverzeichnis: {self.main_dir.get()}") + self.log_message(f"Anzahl Projekte: {len(self.projects)}") + self.log_message(f"Batch-Log: {batch_log}") + self.log_message("") + + success_count = 0 + error_count = 0 + + for i, project in enumerate(self.projects): + self.progress_var.set((i / len(self.projects)) * 100) + self.progress_label.config(text=f"Verarbeite {i+1}/{len(self.projects)}: {project['name']}") + self.update_project_status(i, 'Verarbeite...') + + self.log_message(f"--- Projekt: {project['name']} ---") + + try: + self.process_single_project(project) + self.update_project_status(i, '✓ Erfolgreich') + self.log_message(f"✓ {project['name']} erfolgreich verarbeitet") + success_count += 1 + except Exception as e: + self.update_project_status(i, '✗ Fehler') + self.log_message(f"✗ Fehler bei {project['name']}: {str(e)}", 'error') + error_count += 1 + + self.log_message("") + + self.progress_var.set(100) + self.progress_label.config(text="Fertig") + + # Summary + self.log_message("=== ZUSAMMENFASSUNG ===") + self.log_message(f"Gesamt: {len(self.projects)}") + self.log_message(f"Erfolgreich: {success_count}") + self.log_message(f"Fehlgeschlagen: {error_count}") + + self.processing = False + + messagebox.showinfo( + "Batch-Verarbeitung abgeschlossen", + f"Verarbeitung abgeschlossen!\n\n" + f"Erfolgreich: {success_count}\n" + f"Fehlgeschlagen: {error_count}\n\n" + f"Details siehe: {batch_log}" + ) + + def process_single_project(self, project): + """Process a single project without GUI interaction.""" + root_dir = project['path'] + pointcloud_dir = project['pointcloud'] + lsdx_file = project['lsdx'] + + # Setup project-specific logging + project_logger = logging.getLogger(f'Project_{project["name"]}') + project_logger.setLevel(logging.INFO) + project_logger.handlers.clear() + + log_file = os.path.join(root_dir, "renamer.log") + fh = logging.FileHandler(log_file, encoding='utf-8') + fh.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + fh.setFormatter(formatter) + project_logger.addHandler(fh) + + project_logger.info(f"Batch-Verarbeitung gestartet für: {root_dir}") + + # Get names + root_name = os.path.basename(root_dir) + cluster_name, removed_strings = self.clean_cluster_name(root_name) + scan_prefix = root_name + + project_logger.info(f"Clustername: {root_name} → {cluster_name}") + project_logger.info(f"Scan-Namen-Präfix: {scan_prefix}") + if removed_strings: + project_logger.info(f"Entfernte Strings: {', '.join(removed_strings)}") + + # Find preview dir + preview_dir = os.path.join(pointcloud_dir, "Previews") + if not os.path.isdir(preview_dir): + preview_dir = None + + # Get LSD files and mapping + lsd_files = [f for f in os.listdir(pointcloud_dir) if f.endswith('.lsd')] + lsd_mapping, lsd_width = self.get_file_mapping(lsd_files, 'lsd', scan_prefix) + + # Get PNG files and mapping + png_mapping = {} + png_width = lsd_width + if preview_dir: + png_files = [f for f in os.listdir(preview_dir) if f.endswith('.png')] + png_mapping, png_width = self.get_file_mapping(png_files, 'png', scan_prefix) + + # Create backup + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_name = f"{os.path.basename(lsdx_file)}.backup_{timestamp}" + backup_path = os.path.join(pointcloud_dir, backup_name) + shutil.copy2(lsdx_file, backup_path) + project_logger.info(f"Backup erstellt: {backup_name}") + + # Rename LSD files + if lsd_mapping: + self.safe_rename(pointcloud_dir, lsd_mapping, project_logger, "LSD") + + # Rename PNG files + if preview_dir and png_mapping: + self.safe_rename(preview_dir, png_mapping, project_logger, "PNG") + + # Update LSDX file + self.update_lsdx_file(lsdx_file, { + 'cluster_name': cluster_name, + 'scan_prefix': scan_prefix, + 'original_name': root_name, + 'removed_strings': removed_strings, + 'lsd_width': lsd_width, + 'png_width': png_width + }, project_logger) + + project_logger.info("Verarbeitung abgeschlossen") + + def get_file_mapping(self, file_list, extension, scan_prefix): + """Create mapping for files with full scan names.""" + files_with_nums = [] + for f in file_list: + match = re.match(r'^(\d+)\.' + extension + '$', f) + if match: + num = int(match.group(1)) + files_with_nums.append((num, f)) + + if not files_with_nums: + return {}, 2 + + files_with_nums.sort(key=lambda x: x[0]) + max_num = max(num for num, _ in files_with_nums) + + width = 3 if max_num >= 100 else 2 + + mapping = {} + for num, old_name in files_with_nums: + new_num_str = str(num).zfill(width) + new_name = f"{scan_prefix}_{new_num_str}.{extension}" + if old_name != new_name: + mapping[old_name] = new_name + + return mapping, width + + def safe_rename(self, directory, mapping, logger, file_type): + """Rename files using temporary names to avoid conflicts.""" + temp_suffix = f"_temp_{datetime.now().strftime('%Y%m%d%H%M%S')}" + + temp_mappings = [] + for old_name, new_name in mapping.items(): + src = os.path.join(directory, old_name) + if os.path.exists(src): + temp_name = src + temp_suffix + os.rename(src, temp_name) + temp_mappings.append((temp_name, os.path.join(directory, new_name))) + logger.info(f"{file_type}: {old_name} → temp") + + for temp_name, dst in temp_mappings: + os.rename(temp_name, dst) + logger.info(f"{file_type}: temp → {os.path.basename(dst)}") + + def update_lsdx_file(self, lsdx_path, params, logger): + """Update the LSDX file with new filenames, cluster name, and scan names.""" + cluster_name = params['cluster_name'] + scan_prefix = params.get('scan_prefix', params.get('original_name', cluster_name)) + removed_strings = params.get('removed_strings', []) + lsd_width = params['lsd_width'] + png_width = params['png_width'] + + tree = ET.parse(lsdx_path) + root = tree.getroot() + + # Update cluster name + for element in root.findall(".//Element[@type='cluster']"): + old_cluster = element.get('name') + element.set('name', cluster_name) + logger.info(f"Clustername: '{old_cluster}' → '{cluster_name}'") + if removed_strings: + logger.info(f" Entfernte Strings: {', '.join(removed_strings)}") + + # Update scan names with prefix + for element in root.findall(".//Element[@type='scan']"): + old_name = element.get('name') + if old_name: + match = re.match(r'^(\d+)$', old_name) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(lsd_width) + new_name = f"{scan_prefix}_{new_num_str}" + element.set('name', new_name) + logger.info(f"Scan-Name: '{old_name}' → '{new_name}'") + + # Update FilePath elements + for filepath in root.findall(".//FilePath"): + file_type = filepath.get('type') + text = filepath.text + + if file_type == 'lsd' and text: + match = re.match(r'^(\d+)\.lsd$', text) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(lsd_width) + new_name = f"{scan_prefix}_{new_num_str}.lsd" + filepath.text = new_name + logger.info(f"LSDX FilePath: {text} → {new_name}") + + elif file_type == 'preview' and text: + match = re.match(r'^Previews/(\d+)\.png$', text) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(png_width) + new_name = f"Previews/{scan_prefix}_{new_num_str}.png" + filepath.text = new_name + logger.info(f"LSDX FilePath: {text} → {new_name}") + + tree.write(lsdx_path, encoding='utf-8', xml_declaration=True) + + # Add DOCTYPE back + with open(lsdx_path, 'r', encoding='utf-8') as f: + content = f.read() + + if '' not in content: + content = content.replace( + "", + "\n" + ) + with open(lsdx_path, 'w', encoding='utf-8') as f: + f.write(content) + + logger.info("LSDX-Datei erfolgreich aktualisiert") + + +class ProjectMerger: + """Project Merger for combining multiple PointCab projects into one.""" + + def __init__(self, root, return_callback=None): + self.root = root + self.return_callback = return_callback + self.root.title("PointCab Projektmerger") + self.root.geometry("1000x800") + self.root.minsize(900, 700) + + # Variables + self.target_dir = tk.StringVar() + self.source_dir = tk.StringVar() + self.merge_mode = tk.StringVar(value="single") + self.target_project = None + self.source_projects = [] + self.merge_logger = None + self.processing = False + + self.setup_ui() + + def setup_ui(self): + """Setup the project merger UI.""" + main_frame = ttk.Frame(self.root, padding="10") + main_frame.pack(fill=tk.BOTH, expand=True) + + # Back button + if self.return_callback: + back_frame = ttk.Frame(main_frame) + back_frame.pack(fill=tk.X, pady=(0, 10)) + ttk.Button(back_frame, text="← Zurück zum Hauptmenü", command=self.go_back).pack(side=tk.LEFT) + + # Target project selection + target_frame = ttk.LabelFrame(main_frame, text="🎯 Stammprojekt (Ziel)", padding="5") + target_frame.pack(fill=tk.X, pady=(0, 10)) + + target_entry_frame = ttk.Frame(target_frame) + target_entry_frame.pack(fill=tk.X) + ttk.Entry(target_entry_frame, textvariable=self.target_dir, width=60).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) + ttk.Button(target_entry_frame, text="Durchsuchen...", command=self.select_target).pack(side=tk.LEFT) + ttk.Button(target_entry_frame, text="Analysieren", command=self.analyze_target).pack(side=tk.LEFT, padx=(5, 0)) + + self.target_info = ttk.Label(target_frame, text="Bitte Stammprojekt auswählen", foreground="gray") + self.target_info.pack(fill=tk.X, pady=(5, 0)) + + # Mode selection + mode_frame = ttk.LabelFrame(main_frame, text="Merge-Modus", padding="5") + mode_frame.pack(fill=tk.X, pady=(0, 10)) + + ttk.Radiobutton(mode_frame, text="📄 Einzelprojekt hinzufügen", variable=self.merge_mode, + value="single", command=self.on_mode_change).pack(side=tk.LEFT, padx=(0, 20)) + ttk.Radiobutton(mode_frame, text="📂 Batch-Merge (mehrere Projekte)", variable=self.merge_mode, + value="batch", command=self.on_mode_change).pack(side=tk.LEFT) + + # Source project selection + source_frame = ttk.LabelFrame(main_frame, text="📁 Quellprojekt(e)", padding="5") + source_frame.pack(fill=tk.X, pady=(0, 10)) + + source_entry_frame = ttk.Frame(source_frame) + source_entry_frame.pack(fill=tk.X) + ttk.Entry(source_entry_frame, textvariable=self.source_dir, width=60).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) + ttk.Button(source_entry_frame, text="Durchsuchen...", command=self.select_source).pack(side=tk.LEFT) + ttk.Button(source_entry_frame, text="Projekte suchen", command=self.scan_source).pack(side=tk.LEFT, padx=(5, 0)) + + self.source_mode_label = ttk.Label(source_frame, text="Wählen Sie ein Quellprojekt aus", foreground="gray") + self.source_mode_label.pack(fill=tk.X, pady=(5, 0)) + + # Source projects list + projects_frame = ttk.LabelFrame(main_frame, text="Zu mergende Projekte", padding="5") + projects_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + columns = ('status', 'project', 'scans', 'lsd_files', 'png_files') + self.projects_tree = ttk.Treeview(projects_frame, columns=columns, show='headings', height=8) + self.projects_tree.heading('status', text='Status') + self.projects_tree.heading('project', text='Projekt') + self.projects_tree.heading('scans', text='Scans') + self.projects_tree.heading('lsd_files', text='LSD-Dateien') + self.projects_tree.heading('png_files', text='PNG-Dateien') + self.projects_tree.column('status', width=100) + self.projects_tree.column('project', width=250) + self.projects_tree.column('scans', width=80) + self.projects_tree.column('lsd_files', width=100) + self.projects_tree.column('png_files', width=100) + + scrollbar = ttk.Scrollbar(projects_frame, orient=tk.VERTICAL, command=self.projects_tree.yview) + self.projects_tree.configure(yscrollcommand=scrollbar.set) + + self.projects_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # Preview frame + preview_frame = ttk.LabelFrame(main_frame, text="Vorschau der Merge-Operationen", padding="5") + preview_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + self.preview_text = scrolledtext.ScrolledText(preview_frame, wrap=tk.WORD, height=10, font=('Consolas', 9)) + self.preview_text.pack(fill=tk.BOTH, expand=True) + self.preview_text.config(state=tk.DISABLED) + + # Progress section + progress_frame = ttk.LabelFrame(main_frame, text="Fortschritt", padding="5") + progress_frame.pack(fill=tk.X, pady=(0, 10)) + + self.progress_var = tk.DoubleVar() + self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100) + self.progress_bar.pack(fill=tk.X, pady=(0, 5)) + + self.progress_label = ttk.Label(progress_frame, text="") + self.progress_label.pack(fill=tk.X) + + # Buttons + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=tk.X) + + self.preview_btn = ttk.Button(button_frame, text="Vorschau", command=self.show_preview, state=tk.DISABLED) + self.preview_btn.pack(side=tk.RIGHT, padx=(5, 0)) + + self.merge_btn = ttk.Button(button_frame, text="🔀 Merge durchführen", command=self.execute_merge, state=tk.DISABLED) + self.merge_btn.pack(side=tk.RIGHT) + + ttk.Button(button_frame, text="Beenden", command=self.root.quit).pack(side=tk.LEFT) + + def go_back(self): + """Return to main menu.""" + if self.processing: + messagebox.showwarning("Warnung", "Bitte warten Sie, bis der Merge abgeschlossen ist.") + return + if self.return_callback: + self.return_callback() + + def on_mode_change(self): + """Handle mode change.""" + if self.merge_mode.get() == "single": + self.source_mode_label.config(text="Wählen Sie ein einzelnes Quellprojekt aus") + else: + self.source_mode_label.config(text="Wählen Sie ein Hauptverzeichnis mit mehreren Quellprojekten aus") + self.source_projects = [] + self.projects_tree.delete(*self.projects_tree.get_children()) + self.update_buttons() + + def select_target(self): + """Select target project directory.""" + directory = filedialog.askdirectory(title="Stammprojekt auswählen") + if directory: + self.target_dir.set(directory) + self.analyze_target() + + def select_source(self): + """Select source project/directory.""" + directory = filedialog.askdirectory(title="Quellprojekt(e) auswählen") + if directory: + self.source_dir.set(directory) + + def find_pointcloud_folder(self, root_dir): + """Find the PointCloud folder in the root directory.""" + root_name = os.path.basename(root_dir) + expected_name = f"{root_name}_PointCloud" + + expected_path = os.path.join(root_dir, expected_name) + if os.path.isdir(expected_path): + return expected_path + + for item in os.listdir(root_dir): + item_path = os.path.join(root_dir, item) + if os.path.isdir(item_path) and item.endswith("_PointCloud"): + return item_path + + return None + + def find_lsdx_file(self, pointcloud_dir): + """Find the LSDX file in the PointCloud directory.""" + for item in os.listdir(pointcloud_dir): + if item.endswith(".lsdx"): + return os.path.join(pointcloud_dir, item) + return None + + def count_files(self, directory, extension): + """Count files with given extension.""" + if not directory or not os.path.isdir(directory): + return 0 + return len([f for f in os.listdir(directory) if f.endswith(extension)]) + + def get_files(self, directory, extension): + """Get list of files with given extension.""" + if not directory or not os.path.isdir(directory): + return [] + return [f for f in os.listdir(directory) if f.endswith(extension)] + + def analyze_target(self): + """Analyze the target project.""" + target_dir = self.target_dir.get() + + if not target_dir or not os.path.isdir(target_dir): + messagebox.showerror("Fehler", "Bitte wählen Sie ein gültiges Stammprojekt aus.") + return + + pointcloud_dir = self.find_pointcloud_folder(target_dir) + if not pointcloud_dir: + messagebox.showerror("Fehler", "Kein PointCloud-Ordner im Stammprojekt gefunden!") + self.target_project = None + self.update_buttons() + return + + lsdx_file = self.find_lsdx_file(pointcloud_dir) + if not lsdx_file: + messagebox.showerror("Fehler", "Keine LSDX-Datei im Stammprojekt gefunden!") + self.target_project = None + self.update_buttons() + return + + preview_dir = os.path.join(pointcloud_dir, "Previews") + if not os.path.isdir(preview_dir): + preview_dir = None + + lsd_count = self.count_files(pointcloud_dir, '.lsd') + png_count = self.count_files(preview_dir, '.png') if preview_dir else 0 + + # Count scans in LSDX + scan_count = 0 + try: + tree = ET.parse(lsdx_file) + root = tree.getroot() + scan_count = len(root.findall(".//Element[@type='scan']")) + except: + pass + + self.target_project = { + 'path': target_dir, + 'name': os.path.basename(target_dir), + 'pointcloud': pointcloud_dir, + 'preview': preview_dir, + 'lsdx': lsdx_file, + 'scan_count': scan_count, + 'lsd_count': lsd_count, + 'png_count': png_count + } + + info = f"✓ {self.target_project['name']} | Scans: {scan_count} | LSD: {lsd_count} | PNG: {png_count}" + self.target_info.config(text=info, foreground="green") + self.update_buttons() + + def scan_source(self): + """Scan source directory for projects.""" + source_dir = self.source_dir.get() + + if not source_dir or not os.path.isdir(source_dir): + messagebox.showerror("Fehler", "Bitte wählen Sie ein gültiges Quellverzeichnis aus.") + return + + self.source_projects = [] + self.projects_tree.delete(*self.projects_tree.get_children()) + + if self.merge_mode.get() == "single": + # Single project mode + project = self.analyze_source_project(source_dir) + if project: + self.source_projects.append(project) + self.projects_tree.insert('', tk.END, values=( + 'Bereit', project['name'], project['scan_count'], + project['lsd_count'], project['png_count'] + )) + else: + messagebox.showerror("Fehler", "Kein gültiges PointCab-Projekt gefunden!") + else: + # Batch mode - scan subdirectories + for item in sorted(os.listdir(source_dir)): + item_path = os.path.join(source_dir, item) + if os.path.isdir(item_path): + # Skip if this is the target project + if self.target_project and os.path.samefile(item_path, self.target_project['path']): + continue + project = self.analyze_source_project(item_path) + if project: + self.source_projects.append(project) + self.projects_tree.insert('', tk.END, values=( + 'Bereit', project['name'], project['scan_count'], + project['lsd_count'], project['png_count'] + )) + + if self.source_projects: + self.source_mode_label.config(text=f"{len(self.source_projects)} Quellprojekt(e) gefunden", foreground="green") + else: + self.source_mode_label.config(text="Keine gültigen Quellprojekte gefunden", foreground="red") + + self.update_buttons() + + def analyze_source_project(self, project_dir): + """Analyze a source project and return its info.""" + pointcloud_dir = self.find_pointcloud_folder(project_dir) + if not pointcloud_dir: + return None + + lsdx_file = self.find_lsdx_file(pointcloud_dir) + if not lsdx_file: + return None + + preview_dir = os.path.join(pointcloud_dir, "Previews") + if not os.path.isdir(preview_dir): + preview_dir = None + + lsd_count = self.count_files(pointcloud_dir, '.lsd') + png_count = self.count_files(preview_dir, '.png') if preview_dir else 0 + + # Count scans in LSDX + scan_count = 0 + try: + tree = ET.parse(lsdx_file) + root = tree.getroot() + scan_count = len(root.findall(".//Element[@type='scan']")) + except: + pass + + return { + 'path': project_dir, + 'name': os.path.basename(project_dir), + 'pointcloud': pointcloud_dir, + 'preview': preview_dir, + 'lsdx': lsdx_file, + 'scan_count': scan_count, + 'lsd_count': lsd_count, + 'png_count': png_count, + 'lsd_files': self.get_files(pointcloud_dir, '.lsd'), + 'png_files': self.get_files(preview_dir, '.png') if preview_dir else [] + } + + def update_buttons(self): + """Update button states based on current selection.""" + if self.target_project and self.source_projects: + self.preview_btn.config(state=tk.NORMAL) + self.merge_btn.config(state=tk.NORMAL) + else: + self.preview_btn.config(state=tk.DISABLED) + self.merge_btn.config(state=tk.DISABLED) + + def get_conflict_resolved_name(self, filename, existing_files, suffix_counter): + """Get a conflict-resolved filename.""" + base, ext = os.path.splitext(filename) + new_name = filename + + while new_name in existing_files: + suffix_counter[0] += 1 + new_name = f"{base}_merged_{suffix_counter[0]}{ext}" + + return new_name + + def show_preview(self): + """Show preview of merge operations.""" + if not self.target_project or not self.source_projects: + return + + preview_text = "=== MERGE-VORSCHAU ===\n\n" + preview_text += f"🎯 STAMMPROJEKT: {self.target_project['name']}\n" + preview_text += f" Aktuelle Scans: {self.target_project['scan_count']}\n" + preview_text += f" LSD-Dateien: {self.target_project['lsd_count']}\n" + preview_text += f" PNG-Dateien: {self.target_project['png_count']}\n\n" + + # Get existing files in target + existing_lsd = set(self.get_files(self.target_project['pointcloud'], '.lsd')) + existing_png = set(self.get_files(self.target_project['preview'], '.png')) if self.target_project['preview'] else set() + + total_new_scans = 0 + total_new_lsd = 0 + total_new_png = 0 + + for project in self.source_projects: + preview_text += f"📁 QUELLPROJEKT: {project['name']}\n" + preview_text += f" Scans: {project['scan_count']}\n" + + # Check for LSD conflicts + lsd_conflicts = 0 + for lsd in project.get('lsd_files', []): + if lsd in existing_lsd: + lsd_conflicts += 1 + + preview_text += f" LSD-Dateien: {project['lsd_count']}" + if lsd_conflicts > 0: + preview_text += f" ({lsd_conflicts} Namenskonflikte → werden umbenannt)" + preview_text += "\n" + + # Check for PNG conflicts + png_conflicts = 0 + for png in project.get('png_files', []): + if png in existing_png: + png_conflicts += 1 + + preview_text += f" PNG-Dateien: {project['png_count']}" + if png_conflicts > 0: + preview_text += f" ({png_conflicts} Namenskonflikte → werden umbenannt)" + preview_text += "\n\n" + + total_new_scans += project['scan_count'] + total_new_lsd += project['lsd_count'] + total_new_png += project['png_count'] + + # Add to existing for next iteration + for lsd in project.get('lsd_files', []): + existing_lsd.add(lsd) + for png in project.get('png_files', []): + existing_png.add(png) + + preview_text += "=== ZUSAMMENFASSUNG ===\n" + preview_text += f"Neue Scans: {total_new_scans}\n" + preview_text += f"Neue LSD-Dateien: {total_new_lsd}\n" + preview_text += f"Neue PNG-Dateien: {total_new_png}\n" + preview_text += f"\nNach Merge:\n" + preview_text += f" Scans gesamt: {self.target_project['scan_count'] + total_new_scans}\n" + preview_text += f" LSD-Dateien: {self.target_project['lsd_count'] + total_new_lsd}\n" + preview_text += f" PNG-Dateien: {self.target_project['png_count'] + total_new_png}\n" + + preview_text += "\n⚠️ HINWEIS:\n" + preview_text += " - Ein Backup der Stammprojekt-LSDX wird erstellt\n" + preview_text += " - Bei Namenskonflikten werden Dateien mit Suffix '_merged_N' umbenannt\n" + preview_text += " - Alle Referenzen in der LSDX werden entsprechend aktualisiert\n" + + self.preview_text.config(state=tk.NORMAL) + self.preview_text.delete(1.0, tk.END) + self.preview_text.insert(tk.END, preview_text) + self.preview_text.config(state=tk.DISABLED) + + def setup_merge_logging(self, target_dir): + """Setup merge logging.""" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + log_file = os.path.join(target_dir, f"merge_{timestamp}.log") + + self.merge_logger = logging.getLogger(f'ProjectMerger_{timestamp}') + self.merge_logger.setLevel(logging.INFO) + self.merge_logger.handlers.clear() + + fh = logging.FileHandler(log_file, encoding='utf-8') + fh.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + fh.setFormatter(formatter) + self.merge_logger.addHandler(fh) + + return log_file + + def log_message(self, message, level='info'): + """Log message.""" + if self.merge_logger: + if level == 'error': + self.merge_logger.error(message) + else: + self.merge_logger.info(message) + + def update_project_status(self, index, status): + """Update project status in treeview.""" + if index < len(self.projects_tree.get_children()): + item_id = self.projects_tree.get_children()[index] + values = list(self.projects_tree.item(item_id)['values']) + values[0] = status + self.projects_tree.item(item_id, values=values) + self.root.update_idletasks() + + def execute_merge(self): + """Execute the merge operation.""" + if not self.target_project or not self.source_projects: + return + + result = messagebox.askyesno( + "Bestätigung", + f"Möchten Sie wirklich {len(self.source_projects)} Projekt(e) in das Stammprojekt mergen?\n\n" + "Ein Backup der LSDX-Datei wird automatisch erstellt." + ) + + if not result: + return + + self.processing = True + self.merge_btn.config(state=tk.DISABLED) + self.preview_btn.config(state=tk.DISABLED) + + # Setup logging + log_file = self.setup_merge_logging(self.target_project['path']) + + self.log_message("=== MERGE GESTARTET ===") + self.log_message(f"Stammprojekt: {self.target_project['name']}") + self.log_message(f"Quellprojekte: {len(self.source_projects)}") + self.log_message(f"Log-Datei: {log_file}") + + # Update preview with log + self.preview_text.config(state=tk.NORMAL) + self.preview_text.delete(1.0, tk.END) + self.preview_text.insert(tk.END, f"=== MERGE LÄUFT ===\n\nLog: {log_file}\n\n") + + try: + # Create backup of target LSDX + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_path = f"{self.target_project['lsdx']}.backup_{timestamp}" + shutil.copy2(self.target_project['lsdx'], backup_path) + self.log_message(f"Backup erstellt: {backup_path}") + self.preview_text.insert(tk.END, f"✓ Backup erstellt\n") + + # Parse target LSDX + target_tree = ET.parse(self.target_project['lsdx']) + target_root = target_tree.getroot() + target_elements = target_root.find('Elements') + + # Get existing files + existing_lsd = set(self.get_files(self.target_project['pointcloud'], '.lsd')) + existing_png = set(self.get_files(self.target_project['preview'], '.png')) if self.target_project['preview'] else set() + + # Track UUID mappings for parent references + suffix_counter = [0] + + success_count = 0 + error_count = 0 + + for i, project in enumerate(self.source_projects): + self.progress_var.set((i / len(self.source_projects)) * 100) + self.progress_label.config(text=f"Merging {i+1}/{len(self.source_projects)}: {project['name']}") + self.update_project_status(i, 'Verarbeite...') + self.root.update_idletasks() + + try: + self.merge_single_project( + project, + target_elements, + existing_lsd, + existing_png, + suffix_counter + ) + self.update_project_status(i, '✓ Erfolgreich') + self.preview_text.insert(tk.END, f"✓ {project['name']} gemerged\n") + success_count += 1 + except Exception as e: + self.update_project_status(i, '✗ Fehler') + self.preview_text.insert(tk.END, f"✗ Fehler bei {project['name']}: {e}\n") + self.log_message(f"Fehler bei {project['name']}: {e}", 'error') + error_count += 1 + + self.preview_text.see(tk.END) + self.root.update_idletasks() + + # Zähle finale Elemente vor dem Speichern + final_clusters = len(target_elements.findall("Element[@type='cluster']")) + final_scans = len(target_elements.findall("Element[@type='scan']")) + self.log_message(f"") + self.log_message(f"{'='*60}") + self.log_message(f"FINALE LSDX STATISTIK") + self.log_message(f"{'='*60}") + self.log_message(f" Cluster gesamt: {final_clusters}") + self.log_message(f" Scans gesamt: {final_scans}") + + # Save merged LSDX + target_tree.write(self.target_project['lsdx'], encoding='utf-8', xml_declaration=True) + + # Add DOCTYPE back + with open(self.target_project['lsdx'], 'r', encoding='utf-8') as f: + content = f.read() + if '' not in content: + content = content.replace( + "", + "\n" + ) + with open(self.target_project['lsdx'], 'w', encoding='utf-8') as f: + f.write(content) + + self.log_message("LSDX-Datei gespeichert") + self.log_message(f"Pfad: {self.target_project['lsdx']}") + + self.progress_var.set(100) + self.progress_label.config(text="Fertig") + + # Summary + self.log_message("=== MERGE ABGESCHLOSSEN ===") + self.log_message(f"Erfolgreich: {success_count}") + self.log_message(f"Fehlgeschlagen: {error_count}") + + self.preview_text.insert(tk.END, f"\n=== ZUSAMMENFASSUNG ===\n") + self.preview_text.insert(tk.END, f"Erfolgreich: {success_count}\n") + self.preview_text.insert(tk.END, f"Fehlgeschlagen: {error_count}\n") + self.preview_text.insert(tk.END, f"Log: {log_file}\n") + self.preview_text.config(state=tk.DISABLED) + + messagebox.showinfo( + "Merge abgeschlossen", + f"Merge abgeschlossen!\n\n" + f"Erfolgreich: {success_count}\n" + f"Fehlgeschlagen: {error_count}\n\n" + f"Details siehe: {log_file}" + ) + + except Exception as e: + self.log_message(f"Kritischer Fehler: {e}", 'error') + messagebox.showerror("Fehler", f"Fehler beim Merge:\n{e}") + + finally: + self.processing = False + self.merge_btn.config(state=tk.NORMAL) + self.preview_btn.config(state=tk.NORMAL) + + def merge_single_project(self, source_project, target_elements, existing_lsd, existing_png, suffix_counter): + """ + Merge a single source project into target. + + LSDX Struktur: + - Registration (Wurzel, parents="") + - Cluster (parents=registration_uuid, name="ClusterName") + - Scan (parents=cluster_uuid, name="1", enthält FilePath-Referenzen) + + Merge-Logik: + 1. Finde Target-Registration UUID + 2. Für jeden Cluster im Quellprojekt: + - Prüfe ob Cluster mit gleichem Namen im Ziel existiert + - Wenn ja: Verwende existierende Cluster-UUID für Scans + - Wenn nein: Füge neuen Cluster hinzu mit neuer UUID + 3. Für jeden Scan: Füge hinzu mit korrekter Cluster-UUID als Parent + """ + self.log_message(f"") + self.log_message(f"{'='*60}") + self.log_message(f"MERGE START: {source_project['name']}") + self.log_message(f"{'='*60}") + + # Parse source LSDX + source_tree = ET.parse(source_project['lsdx']) + source_root = source_tree.getroot() + source_elements = source_root.find('Elements') + + if source_elements is None: + self.log_message("FEHLER: Keine Elements in Quell-LSDX gefunden!") + raise ValueError("Keine Elements in Quell-LSDX") + + # === SCHRITT 1: Finde Target-Registration UUID === + target_registration_uuid = None + for elem in target_elements.findall("Element[@type='registration']"): + target_registration_uuid = elem.get('uuid') + self.log_message(f"[TARGET] Registration UUID: {target_registration_uuid}") + break + + if not target_registration_uuid: + self.log_message("FEHLER: Keine Registration im Ziel gefunden!") + raise ValueError("Keine Registration im Ziel-LSDX") + + # === SCHRITT 2: Sammle existierende Cluster im Target === + target_clusters = {} # name -> uuid + for elem in target_elements.findall("Element[@type='cluster']"): + cluster_name = elem.get('name', '') + cluster_uuid = elem.get('uuid') + target_clusters[cluster_name] = cluster_uuid + self.log_message(f"[TARGET] Existierender Cluster: '{cluster_name}' UUID={cluster_uuid}") + + self.log_message(f"[TARGET] Cluster gesamt: {len(target_clusters)}") + + # === SCHRITT 3: Analysiere Source-Struktur === + source_registration_uuid = None + source_clusters = {} # source_uuid -> {name, scans: []} + source_scans = [] + + for elem in source_elements.findall("Element"): + elem_type = elem.get('type') + elem_uuid = elem.get('uuid') + elem_name = elem.get('name', '') + elem_parents = elem.get('parents', '') + + if elem_type == 'registration': + source_registration_uuid = elem_uuid + self.log_message(f"[SOURCE] Registration UUID: {elem_uuid}") + elif elem_type == 'cluster': + source_clusters[elem_uuid] = {'name': elem_name, 'parent': elem_parents, 'scans': []} + self.log_message(f"[SOURCE] Cluster: '{elem_name}' UUID={elem_uuid} parent={elem_parents}") + elif elem_type == 'scan': + source_scans.append({'elem': elem, 'uuid': elem_uuid, 'name': elem_name, 'parent': elem_parents}) + self.log_message(f"[SOURCE] Scan: '{elem_name}' UUID={elem_uuid} parent={elem_parents}") + + self.log_message(f"[SOURCE] Cluster: {len(source_clusters)}, Scans: {len(source_scans)}") + + # === SCHRITT 4: Ordne Scans den Clustern zu === + for scan in source_scans: + scan_parent = scan['parent'] + if scan_parent in source_clusters: + source_clusters[scan_parent]['scans'].append(scan) + else: + self.log_message(f"WARNUNG: Scan '{scan['name']}' hat unbekannten Parent: {scan_parent}") + + # === SCHRITT 5: Dateien kopieren === + lsd_renames = {} # old_name -> new_name + png_renames = {} # old_name -> new_name + + self.log_message(f"") + self.log_message(f"--- Kopiere LSD-Dateien ---") + for lsd_file in source_project.get('lsd_files', []): + src_path = os.path.join(source_project['pointcloud'], lsd_file) + new_name = self.get_conflict_resolved_name(lsd_file, existing_lsd, suffix_counter) + dst_path = os.path.join(self.target_project['pointcloud'], new_name) + + if os.path.exists(src_path): + shutil.copy2(src_path, dst_path) + existing_lsd.add(new_name) + if lsd_file != new_name: + lsd_renames[lsd_file] = new_name + self.log_message(f" LSD kopiert (umbenannt): {lsd_file} → {new_name}") + else: + self.log_message(f" LSD kopiert: {lsd_file}") + + self.log_message(f"--- Kopiere PNG-Dateien ---") + if source_project['preview'] and self.target_project['preview']: + if not os.path.exists(self.target_project['preview']): + os.makedirs(self.target_project['preview']) + + for png_file in source_project.get('png_files', []): + src_path = os.path.join(source_project['preview'], png_file) + new_name = self.get_conflict_resolved_name(png_file, existing_png, suffix_counter) + dst_path = os.path.join(self.target_project['preview'], new_name) + + if os.path.exists(src_path): + shutil.copy2(src_path, dst_path) + existing_png.add(new_name) + if png_file != new_name: + png_renames[png_file] = new_name + self.log_message(f" PNG kopiert (umbenannt): {png_file} → {new_name}") + else: + self.log_message(f" PNG kopiert: {png_file}") + + # === SCHRITT 6: Cluster und Scans hinzufügen === + self.log_message(f"") + self.log_message(f"--- Füge Cluster und Scans hinzu ---") + + cluster_uuid_mapping = {} # source_cluster_uuid -> target_cluster_uuid + clusters_added = 0 + clusters_reused = 0 + scans_added = 0 + + for source_cluster_uuid, cluster_info in source_clusters.items(): + cluster_name = cluster_info['name'] + + # Prüfe ob Cluster mit gleichem Namen im Ziel existiert + if cluster_name in target_clusters: + # Cluster existiert bereits - verwende existierende UUID + target_cluster_uuid = target_clusters[cluster_name] + cluster_uuid_mapping[source_cluster_uuid] = target_cluster_uuid + self.log_message(f" CLUSTER EXISTIERT: '{cluster_name}' → verwende UUID {target_cluster_uuid}") + clusters_reused += 1 + else: + # Neuen Cluster hinzufügen + new_cluster_uuid = "{" + str(uuid.uuid4()) + "}" + cluster_uuid_mapping[source_cluster_uuid] = new_cluster_uuid + + # Finde das Cluster-Element im Source + for elem in source_elements.findall("Element[@type='cluster']"): + if elem.get('uuid') == source_cluster_uuid: + new_cluster = copy.deepcopy(elem) + new_cluster.set('uuid', new_cluster_uuid) + new_cluster.set('parents', target_registration_uuid) # Parent = Target-Registration + target_elements.append(new_cluster) + + # Merke für zukünftige Projekte + target_clusters[cluster_name] = new_cluster_uuid + + self.log_message(f" CLUSTER HINZUGEFÜGT: '{cluster_name}' UUID={new_cluster_uuid} parent={target_registration_uuid}") + clusters_added += 1 + break + + # Füge Scans dieses Clusters hinzu + target_cluster_uuid = cluster_uuid_mapping[source_cluster_uuid] + for scan_info in cluster_info['scans']: + scan_elem = scan_info['elem'] + new_scan_uuid = "{" + str(uuid.uuid4()) + "}" + + new_scan = copy.deepcopy(scan_elem) + new_scan.set('uuid', new_scan_uuid) + new_scan.set('parents', target_cluster_uuid) # Parent = Cluster-UUID + + # Update FilePath references + for filepath in new_scan.findall(".//FilePath"): + file_type = filepath.get('type') + text = filepath.text or '' + + if file_type == 'lsd' and text: + if text in lsd_renames: + old_text = text + filepath.text = lsd_renames[text] + self.log_message(f" FilePath lsd: {old_text} → {filepath.text}") + + elif file_type == 'preview' and text: + if text.startswith("Previews/"): + png_name = text[9:] + if png_name in png_renames: + old_text = text + filepath.text = f"Previews/{png_renames[png_name]}" + self.log_message(f" FilePath preview: {old_text} → {filepath.text}") + + target_elements.append(new_scan) + self.log_message(f" SCAN HINZUGEFÜGT: '{scan_info['name']}' UUID={new_scan_uuid} parent={target_cluster_uuid}") + scans_added += 1 + + # === ZUSAMMENFASSUNG === + self.log_message(f"") + self.log_message(f"--- MERGE ZUSAMMENFASSUNG ---") + self.log_message(f" Cluster hinzugefügt: {clusters_added}") + self.log_message(f" Cluster wiederverwendet: {clusters_reused}") + self.log_message(f" Scans hinzugefügt: {scans_added}") + self.log_message(f" LSD-Dateien kopiert: {len(source_project.get('lsd_files', []))}") + self.log_message(f" PNG-Dateien kopiert: {len(source_project.get('png_files', []))}") + self.log_message(f"{'='*60}") + + +class MainMenu: + """Main menu for selecting between Single Project, Batch mode, and Project Merger.""" + + def __init__(self, root): + self.root = root + self.root.title("PointCab Projekt Umbenenner v4.1") + self.root.geometry("500x450") + self.root.resizable(False, False) + + self.current_frame = None + self.setup_menu() + + def setup_menu(self): + """Setup the main menu.""" + self.clear_window() + self.root.geometry("500x450") + self.root.resizable(False, False) + + main_frame = ttk.Frame(self.root, padding="30") + main_frame.pack(fill=tk.BOTH, expand=True) + + # Title + title_label = ttk.Label(main_frame, text="PointCab Projekt Umbenenner", font=('Helvetica', 18, 'bold')) + title_label.pack(pady=(0, 5)) + + version_label = ttk.Label(main_frame, text="Version 4.1", font=('Helvetica', 10), foreground='gray') + version_label.pack(pady=(0, 30)) + + # Description + desc_label = ttk.Label( + main_frame, + text="Wählen Sie einen Modus:", + font=('Helvetica', 11) + ) + desc_label.pack(pady=(0, 20)) + + # Buttons frame + buttons_frame = ttk.Frame(main_frame) + buttons_frame.pack(fill=tk.X, pady=10) + + # Style for big buttons + style = ttk.Style() + style.configure('Big.TButton', font=('Helvetica', 12), padding=15) + + # Single project button + single_btn = ttk.Button( + buttons_frame, + text="📁 Einzelprojekt bearbeiten", + style='Big.TButton', + command=self.open_single_project + ) + single_btn.pack(fill=tk.X, pady=5) + + single_desc = ttk.Label( + buttons_frame, + text="Ein einzelnes PointCab-Projekt auswählen und verarbeiten", + foreground='gray' + ) + single_desc.pack(pady=(0, 15)) + + # Batch button + batch_btn = ttk.Button( + buttons_frame, + text="📂 Batch Renamer", + style='Big.TButton', + command=self.open_batch_renamer + ) + batch_btn.pack(fill=tk.X, pady=5) + + batch_desc = ttk.Label( + buttons_frame, + text="Mehrere Projekte in einem Hauptverzeichnis automatisch verarbeiten", + foreground='gray' + ) + batch_desc.pack(pady=(0, 15)) + + # Merger button + merger_btn = ttk.Button( + buttons_frame, + text="🔀 Projektmerger", + style='Big.TButton', + command=self.open_project_merger + ) + merger_btn.pack(fill=tk.X, pady=5) + + merger_desc = ttk.Label( + buttons_frame, + text="Mehrere Projekte in ein Stammprojekt zusammenführen", + foreground='gray' + ) + merger_desc.pack(pady=(0, 15)) + + # Exit button + ttk.Button(main_frame, text="Beenden", command=self.root.quit).pack(pady=(20, 0)) + + def clear_window(self): + """Clear all widgets from window.""" + for widget in self.root.winfo_children(): + widget.destroy() + + def open_single_project(self): + """Open single project mode.""" + self.clear_window() + self.root.geometry("850x750") + self.root.resizable(True, True) + PointCabRenamer(self.root, return_callback=self.setup_menu) + + def open_batch_renamer(self): + """Open batch renamer mode.""" + self.clear_window() + self.root.geometry("900x700") + self.root.resizable(True, True) + BatchRenamer(self.root, return_callback=self.setup_menu) + + def open_project_merger(self): + """Open project merger mode.""" + self.clear_window() + self.root.geometry("1000x800") + self.root.resizable(True, True) + ProjectMerger(self.root, return_callback=self.setup_menu) + + +def main(): + """Main entry point.""" + root = tk.Tk() + app = MainMenu(root) + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/pointcab_renamer.spec b/pointcab_renamer.spec new file mode 100644 index 0000000..d6e37a4 --- /dev/null +++ b/pointcab_renamer.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['pointcab_renamer.py'], + pathex=[], + binaries=[], + datas=[('cluster_cleanup.txt', '.')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='pointcab_renamer', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/release/PointCab_Renamer_v4.2.1.zip b/release/PointCab_Renamer_v4.2.1.zip new file mode 100644 index 0000000..0af2ef4 Binary files /dev/null and b/release/PointCab_Renamer_v4.2.1.zip differ diff --git a/release/PointCab_Renamer_v4.2.1/BENUTZERHANDBUCH.md b/release/PointCab_Renamer_v4.2.1/BENUTZERHANDBUCH.md new file mode 100644 index 0000000..053639e --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/BENUTZERHANDBUCH.md @@ -0,0 +1,385 @@ +# PointCab Renamer - Benutzerhandbuch + +**Version 4.1** | Datum: 14. Januar 2026 + +--- + +## Inhaltsverzeichnis + +1. [Einführung](#einführung) +2. [Installation](#installation) +3. [Programmstart](#programmstart) +4. [Die drei Modi](#die-drei-modi) + - [Einzelprojekt-Modus](#einzelprojekt-modus) + - [Batch-Modus](#batch-modus) + - [Projekt-Merger](#projekt-merger) +5. [Konfiguration](#konfiguration) +6. [Troubleshooting](#troubleshooting) +7. [FAQ](#faq) + +--- + +## Einführung + +### Was ist der PointCab Renamer? + +Der **PointCab Renamer** ist ein Werkzeug zur automatischen Umbenennung von PointCab-Projektdateien. Es löst das Problem, dass PointCab-Scandateien oft kryptische Namen haben (z.B. `1.lsd`, `2.lsd`) und benennt diese nach einem einheitlichen Schema um: + +**Format:** `[ClusterName]_[ScanName].[Erweiterung]` + +**Beispiel:** `EG_Flur_scan001.lsd` + +### Hauptfunktionen + +- **Einzelprojekt-Modus**: Ein einzelnes PointCab-Projekt umbenennen +- **Batch-Modus**: Mehrere Projekte gleichzeitig verarbeiten +- **Projekt-Merger**: Mehrere Projekte in ein Zielprojekt zusammenführen +- **Cluster-Bereinigung**: Automatische Entfernung von Suffixen wie `_re`, `_li` aus Clusternamen +- **Detailliertes Logging**: Vollständige Protokollierung aller Änderungen + +--- + +## Installation + +### Windows + +1. Laden Sie die Datei `pointcab_renamer.exe` herunter +2. Speichern Sie die Datei in einem beliebigen Ordner (z.B. `C:\Tools\`) +3. Kopieren Sie die `cluster_cleanup.txt` in denselben Ordner +4. Starten Sie das Programm mit Doppelklick auf die `.exe` + +### Ubuntu/Linux + +1. Laden Sie die Datei `pointcab_renamer` herunter +2. Speichern Sie die Datei in einem beliebigen Ordner (z.B. `/home/benutzer/tools/`) +3. Kopieren Sie die `cluster_cleanup.txt` in denselben Ordner +4. Machen Sie die Datei ausführbar: + ```bash + chmod +x pointcab_renamer + ``` +5. Starten Sie das Programm: + ```bash + ./pointcab_renamer + ``` + +### Aus dem Quellcode (für Entwickler) + +1. Stellen Sie sicher, dass Python 3.8+ installiert ist +2. Laden Sie den Quellcode herunter +3. Starten Sie mit: + ```bash + python pointcab_renamer.py + ``` + +--- + +## Programmstart + +### Hauptmenü + +Nach dem Start erscheint das Hauptmenü mit drei Optionen: + +``` +╔═══════════════════════════════════════════╗ +║ PointCab Renamer v4.1 ║ +╠═══════════════════════════════════════════╣ +║ ║ +║ [Einzelprojekt umbenennen] ║ +║ ║ +║ [Batch-Verarbeitung] ║ +║ ║ +║ [Projekt Merger] ║ +║ ║ +╚═══════════════════════════════════════════╝ +``` + +--- + +## Die drei Modi + +### Einzelprojekt-Modus + +**Verwendung:** Wenn Sie ein einzelnes PointCab-Projekt umbenennen möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Einzelprojekt umbenennen"** +2. Wählen Sie die **LSDX-Projektdatei** aus (z.B. `Am_Upstall_4.lsdx`) +3. Wählen Sie den **PointCloud-Ordner** aus (enthält die `.lsd` Dateien) +4. Das Programm zeigt eine **Vorschau** der Änderungen: + ``` + Vorschau der Umbenennung: + ───────────────────────── + 1.lsd → EG_Flur_scan001.lsd + 2.lsd → EG_Flur_scan002.lsd + 3.lsd → OG_Bad_scan001.lsd + ... + ``` +5. Klicken Sie auf **"Umbenennen starten"** +6. Nach Abschluss wird ein Protokoll angezeigt + +#### Dateistruktur (Vorher → Nachher) + +**Vorher:** +``` +Am_Upstall_4_PointCloud/ +├── 1.lsd +├── 2.lsd +├── 3.lsd +└── ... +``` + +**Nachher:** +``` +Am_Upstall_4_PointCloud/ +├── EG_Flur_scan001.lsd +├── EG_Flur_scan002.lsd +├── OG_Bad_scan001.lsd +└── ... +``` + +--- + +### Batch-Modus + +**Verwendung:** Wenn Sie mehrere PointCab-Projekte auf einmal verarbeiten möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Batch-Verarbeitung"** +2. Wählen Sie den **Basisordner** aus, der alle Projekte enthält +3. Das Programm erkennt automatisch alle PointCab-Projekte: + ``` + Gefundene Projekte: + ───────────────────── + ☑ Projekt_A (15 Scans) + ☑ Projekt_B (22 Scans) + ☑ Projekt_C (8 Scans) + ``` +4. Wählen Sie die gewünschten Projekte aus (oder behalten Sie alle ausgewählt) +5. Klicken Sie auf **"Batch-Verarbeitung starten"** +6. Der Fortschritt wird angezeigt: + ``` + Verarbeite Projekt 1/3: Projekt_A + [████████████░░░░░░░░] 60% + ``` +7. Nach Abschluss wird eine Zusammenfassung angezeigt + +#### Erwartete Ordnerstruktur + +``` +Basisordner/ +├── Projekt_A/ +│ ├── Projekt_A.lsdx +│ └── Projekt_A_PointCloud/ +│ ├── 1.lsd +│ └── ... +├── Projekt_B/ +│ ├── Projekt_B.lsdx +│ └── Projekt_B_PointCloud/ +└── Projekt_C/ + ├── Projekt_C.lsdx + └── Projekt_C_PointCloud/ +``` + +--- + +### Projekt-Merger + +**Verwendung:** Wenn Sie mehrere PointCab-Projekte in ein einziges Projekt zusammenführen möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Projekt Merger"** +2. Wählen Sie den **Modus**: + - **Einzelprojekt zusammenführen**: Ein Quellprojekt → Zielprojekt + - **Batch-Merge**: Mehrere Quellprojekte → Zielprojekt +3. Wählen Sie das **Zielprojekt** (in das zusammengeführt wird) +4. Wählen Sie das/die **Quellprojekt(e)** +5. Das Programm zeigt eine **Vorschau** mit Konfliktauflösung: + ``` + Merge-Vorschau: + ───────────────────── + Zielprojekt: Haupt_Projekt (5 Cluster, 25 Scans) + Quellprojekt: Teil_A (2 Cluster, 10 Scans) + + Zu übertragende Dateien: + - EG_Flur_scan001.lsd + - EG_Flur_scan002.lsd (Konflikt → EG_Flur_scan002_merged_1.lsd) + - ... + ``` +6. Klicken Sie auf **"Merge starten"** +7. Nach Abschluss werden die Statistiken angezeigt: + ``` + Merge abgeschlossen! + ───────────────────── + Cluster vorher: 5 → nachher: 7 + Scans vorher: 25 → nachher: 35 + Dateien kopiert: 10 + Konflikte gelöst: 1 + ``` + +#### Konfliktauflösung + +Wenn eine Datei im Zielprojekt bereits existiert: +- Die neue Datei wird umbenannt: `dateiname_merged_1.lsd` +- Bei weiteren Konflikten: `dateiname_merged_2.lsd`, etc. +- Die LSDX-Datei wird entsprechend aktualisiert + +#### Wichtige Hinweise + +- **Backup**: Das Zielprojekt wird vor dem Merge gesichert (`.lsdx.backup`) +- **UUID-Regenerierung**: Alle übertragenen Elemente erhalten neue eindeutige IDs +- **Cluster-Duplikate**: Gleichnamige Cluster werden zusammengeführt + +--- + +## Konfiguration + +### Die Datei cluster_cleanup.txt + +Diese Konfigurationsdatei definiert, welche Suffixe aus Clusternamen entfernt werden sollen. + +#### Speicherort + +- **Windows**: Im selben Ordner wie `pointcab_renamer.exe` +- **Linux**: Im selben Ordner wie `pointcab_renamer` + +#### Format + +``` +# Dies ist ein Kommentar +_re +_li +_mi +_mi-li +_mi-re +``` + +- Jede Zeile = ein zu entfernender String +- Zeilen mit `#` am Anfang sind Kommentare +- Leere Zeilen werden ignoriert + +#### Beispiel + +Mit der obigen Konfiguration: +- `Flur_re` → `Flur` +- `Bad_mi-li` → `Bad` +- `Küche_li` → `Küche` + +#### Konfiguration neu laden + +Änderungen an `cluster_cleanup.txt` werden nach einem Neustart oder über den Button **"Konfiguration neu laden"** übernommen. + +--- + +## Troubleshooting + +### Häufige Fehler und Lösungen + +#### "LSDX-Datei nicht gefunden" + +**Problem**: Das Programm kann die Projektdatei nicht finden. + +**Lösung**: +- Stellen Sie sicher, dass die `.lsdx`-Datei im Projektordner existiert +- Prüfen Sie, ob Sie Leserechte für die Datei haben +- Der Dateiname sollte mit `.lsdx` enden (nicht `.LSDX`) + +#### "PointCloud-Ordner nicht gefunden" + +**Problem**: Der Ordner mit den Scandaten fehlt. + +**Lösung**: +- Der Ordner muss `_PointCloud` im Namen haben +- Beispiel: `Projekt_A_PointCloud` +- Prüfen Sie die Ordnerstruktur + +#### "Keine Projekte gefunden" (Batch-Modus) + +**Problem**: Im Basisordner werden keine Projekte erkannt. + +**Lösung**: +- Jedes Projekt muss eine `.lsdx`-Datei und einen `_PointCloud`-Ordner haben +- Der Projektname in der LSDX muss mit dem Ordnernamen übereinstimmen + +#### "Zugriff verweigert" + +**Problem**: Dateien können nicht umbenannt werden. + +**Lösung**: +- Schließen Sie PointCab +- Prüfen Sie, ob andere Programme die Dateien verwenden +- Unter Windows: Als Administrator ausführen +- Unter Linux: Prüfen Sie die Dateiberechtigungen + +#### "GUI startet nicht" (Linux) + +**Problem**: Keine grafische Oberfläche unter Linux. + +**Lösung**: +- Installieren Sie tkinter: `sudo apt install python3-tk` +- Stellen Sie sicher, dass ein Display verfügbar ist + +--- + +## FAQ + +### Allgemeine Fragen + +**F: Werden die Originaldateien gelöscht?** + +A: Nein, die Dateien werden nur umbenannt. Es werden keine Daten gelöscht. + +**F: Kann ich die Umbenennung rückgängig machen?** + +A: Die ursprünglichen Namen werden im Log-Datei protokolliert. Eine automatische Rückgängig-Funktion gibt es nicht. + +**F: Funktioniert das Tool auch mit älteren PointCab-Versionen?** + +A: Das Tool wurde für PointCab-Projekte mit LSDX-Format entwickelt. Ältere Formate werden möglicherweise nicht unterstützt. + +**F: Wie viele Projekte kann ich im Batch-Modus verarbeiten?** + +A: Es gibt keine feste Grenze. Die Verarbeitungszeit hängt von der Anzahl der Scans ab. + +### Technische Fragen + +**F: Wo werden die Log-Dateien gespeichert?** + +A: Im Projektordner, mit dem Format: +- Einzelprojekt: `rename_YYYYMMDD_HHMMSS.log` +- Batch: `batch_YYYYMMDD_HHMMSS.log` +- Merger: `merge_YYYYMMDD_HHMMSS.log` + +**F: Was passiert bei einem Stromausfall während der Verarbeitung?** + +A: Die bereits umbenannten Dateien bleiben umbenannt. Die LSDX-Datei wird erst nach erfolgreicher Umbenennung aktualisiert. + +**F: Kann ich das Tool über die Kommandozeile nutzen?** + +A: Aktuell nur mit grafischer Oberfläche. Kommandozeilen-Unterstützung ist für eine zukünftige Version geplant. + +### Merger-Fragen + +**F: Was passiert mit den Originalprojekten beim Merge?** + +A: Die Quellprojekte werden nicht verändert. Dateien werden kopiert, nicht verschoben. + +**F: Kann ich den Merge rückgängig machen?** + +A: Die ursprüngliche LSDX wird als `.backup` gespeichert. Die kopierten Dateien müssen manuell gelöscht werden. + +**F: Werden alle Unterordner (Previews, etc.) auch gemergt?** + +A: Ja, alle relevanten Unterordner werden übertragen. + +--- + +## Support + +Bei Fragen oder Problemen wenden Sie sich an Ihren Administrator. + +--- + +*PointCab Renamer v4.1 - © 2026* diff --git a/release/PointCab_Renamer_v4.2.1/CHANGELOG.md b/release/PointCab_Renamer_v4.2.1/CHANGELOG.md new file mode 100644 index 0000000..5ae84ce --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/CHANGELOG.md @@ -0,0 +1,171 @@ +# Changelog - PointCab Renamer + +Alle wichtigen Änderungen an diesem Projekt werden hier dokumentiert. + +--- + +## [v4.2.1] - 2026-01-16 + +### Behoben +- **build_linux.sh überarbeitet und getestet** + - Verwendet `python3 -m PyInstaller` für bessere Kompatibilität + - Verbesserte Voraussetzungsprüfungen + - Bessere Fehlerbehandlung und Statusmeldungen + - ✅ GETESTET: Funktioniert erfolgreich + +- **build_windows_wine.sh überarbeitet** + - Bessere Erkennung von Headless-Umgebungen + - Automatische Xvfb-Unterstützung falls verfügbar + - Klare Warnungen zu Wine-Einschränkungen + - Hilfreiche Alternativ-Vorschläge bei Fehlern + - ⚠️ HINWEIS: Wine-Builds in Headless-Umgebungen oft problematisch + +### Dokumentation +- Build-Skript-Versionsnummern auf 4.2.1 aktualisiert +- DEPLOYMENT.md mit Testergebnissen aktualisiert + +### Bekannte Einschränkungen +- Wine-basierte Windows-Builds funktionieren nicht zuverlässig auf Headless-Servern +- Empfehlung: Windows .exe auf echtem Windows-System erstellen + +--- + +## [v4.2] - 2026-01-16 + +### Behoben +- **Windows build_windows.bat komplett überarbeitet** + - Verwendet jetzt `py` statt `python` (Python Launcher für Windows) + - Verwendet `py -m PyInstaller` statt direktem `pyinstaller`-Aufruf + - Korrekte --add-data Syntax für Windows (Semikolon als Trennzeichen) + - Verbesserte Fehlerbehandlung und Statusmeldungen + +- **cluster_cleanup.txt Parser verbessert** + - Unterstützt jetzt UTF-8-BOM (von Windows-Editoren erzeugt) + - Robustere Behandlung von Leerzeilen und Kommentaren + - Gibt jetzt Anzahl geladener Einträge aus + +### Hinzugefügt +- **cluster_cleanup.txt erweitert** + - Neue Einträge: `_part_1`, `_part_2`, `_part_3`, `_part_4`, `_part_5` + +- **Git-Repository Setup** + - `.gitignore` für sauberes Repository + - `GIT_SETUP.md` mit Anleitung für Gitea/GitHub Push + +### Dokumentation +- VERSION.txt aktualisiert +- CHANGELOG.md erweitert + +--- + +## [v4.1.1] - 2026-01-14 + +### Hinzugefügt +- **Cross-Compilation-Unterstützung**: Windows .exe unter Linux erstellen + - `build_windows_on_linux.sh`: Docker-basiertes Build (empfohlen) + - `build_windows_wine.sh`: Wine-basiertes Build (Fallback) + - GitHub Actions Workflow-Beispiel für automatisierte Builds + +### Dokumentation +- DEPLOYMENT.md um Cross-Compilation-Sektion erweitert + - Schritt-für-Schritt-Anleitung für Docker-Methode + - Troubleshooting für häufige Probleme + - Vergleichstabelle der Build-Methoden +- README.md mit Build-Optionen aktualisiert + +--- + +## [v4.1] - 2026-01-14 + +### Behoben +- **Projektmerger LSDx-Zusammenführung komplett überarbeitet** + - Cluster-Duplikat-Erkennung: Verhindert doppelte Cluster bei gleichem Namen + - Scans werden korrekt dem existierenden oder neuen Cluster zugeordnet + - Parent-Referenzen werden korrekt gesetzt (Cluster→Registration, Scan→Cluster) + - Detailliertes Logging aller Merge-Operationen + - Finale Scan/Cluster-Statistik nach Merge + +### Verbessert +- LSDX-Struktur im Code dokumentiert +- Verbesserte Fehlerbehandlung beim Merge + +--- + +## [v4.0] - 2026-01-10 + +### Hinzugefügt +- **Projekt Merger**: Neuer Modus zum Zusammenführen mehrerer PointCab-Projekte + - Einzelprojekt-Merge: Ein Quellprojekt → Zielprojekt + - Batch-Merge: Mehrere Quellprojekte → Zielprojekt + - Intelligente Konfliktauflösung mit `_merged_N` Suffix + - Vollständige LSDX-Zusammenführung (Cluster, Scans, Dateireferenzen) + - UUID-Regenerierung für alle übertragenen Elemente + - Automatisches Backup der Ziel-LSDX vor dem Merge + +### Verbessert +- Neue GUI für den Merger mit Konfliktvorschau +- Batch-Merge mit Fortschrittsanzeige + +--- + +## [v3.1] - 2026-01-05 + +### Geändert +- **Neues Namensformat**: `[ClusterName]_[ScanName].[Erweiterung]` + - Vorher: `[ClusterName].[Erweiterung]` + - Nachher: `EG_Flur_scan001.lsd` +- Scan-Namen werden aus der LSDX extrahiert +- Cluster-Nummer-Duplikate werden vermieden + +--- + +## [v3.0] - 2025-12-20 + +### Hinzugefügt +- **Batch-Verarbeitung**: Mehrere Projekte gleichzeitig umbenennen + - Automatische Projekterkennung im Basisordner + - Selektive Projektauswahl + - Fortschrittsanzeige für Batch-Operationen + - Zusammenfassendes Batch-Log + +### Verbessert +- GUI-Umstrukturierung mit Hauptmenü +- Verbesserte Fehlerbehandlung bei Dateioperationen + +--- + +## [v2.0] - 2025-12-01 + +### Hinzugefügt +- **Cluster-Bereinigung**: Automatische Entfernung von Suffixen + - Konfigurierbar über `cluster_cleanup.txt` + - Entfernt `_re`, `_li`, `_mi`, etc. +- Button "Konfiguration neu laden" + +### Verbessert +- Verbesserte Vorschau der Umbenennung +- Detaillierteres Logging + +--- + +## [v1.0] - 2025-11-15 + +### Erstveröffentlichung +- Grundfunktion: LSDX-Dateien einlesen +- Scans aus PointCloud-Ordner umbenennen +- Grafische Benutzeroberfläche (tkinter) +- Vorschau vor Umbenennung +- Log-Datei-Erstellung + +--- + +## Geplante Features + +- [ ] Kommandozeilen-Unterstützung (CLI-Modus) +- [ ] Rückgängig-Funktion für Umbenennungen +- [ ] Automatische Updates +- [ ] Mehrsprachige Unterstützung (Englisch) + +--- + +*Hinweis: Dieses Changelog folgt dem Format von [Keep a Changelog](https://keepachangelog.com/).* diff --git a/release/PointCab_Renamer_v4.2.1/DEPLOYMENT.md b/release/PointCab_Renamer_v4.2.1/DEPLOYMENT.md new file mode 100644 index 0000000..b31827a --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/DEPLOYMENT.md @@ -0,0 +1,592 @@ +# PointCab Renamer - Deployment-Anleitung + +**Version 4.2.1** | Datum: 16. Januar 2026 + +--- + +## Build-Status (Testergebnisse 2026-01-16) + +| Build-Methode | Status | Hinweise | +|---------------|--------|----------| +| `build_windows.bat` | ✅ Funktioniert | Empfohlen auf Windows | +| `build_linux.sh` | ✅ Getestet | Funktioniert auf Ubuntu 20.04+ | +| `build_windows_wine.sh` | ⚠️ Experimentell | Fehlschläge auf Headless-Servern möglich | +| `build_windows_on_linux.sh` | ⚠️ Docker | Nicht in Docker-in-Docker möglich | + +--- + +## Übersicht + +Diese Anleitung beschreibt, wie Sie aus dem Python-Quellcode ausführbare Dateien für Windows (.exe) und Ubuntu (Binary) erstellen. + +--- + +## Voraussetzungen + +### Benötigte Software + +| Komponente | Windows | Ubuntu | +|------------|---------|--------| +| Python | 3.8+ | 3.8+ | +| PyInstaller | 5.0+ | 5.0+ | +| tkinter | (in Python enthalten) | `python3-tk` | + +### Installation der Voraussetzungen + +#### Windows + +1. **Python installieren:** + - Laden Sie Python von https://www.python.org/downloads/ herunter + - Bei der Installation: ☑ "Add Python to PATH" aktivieren + +2. **PyInstaller installieren:** + ```cmd + pip install pyinstaller + ``` + +#### Ubuntu + +1. **Python und tkinter installieren:** + ```bash + sudo apt update + sudo apt install python3 python3-pip python3-tk + ``` + +2. **PyInstaller installieren:** + ```bash + pip3 install pyinstaller + ``` + +--- + +## Windows-Build (.exe) + +### Automatisch (empfohlen) + +1. Öffnen Sie eine Eingabeaufforderung (cmd) +2. Navigieren Sie zum Projektordner: + ```cmd + cd C:\Pfad\zum\pointcab_renamer + ``` +3. Führen Sie das Build-Skript aus: + ```cmd + build_windows.bat + ``` +4. Die fertige `.exe` finden Sie im Ordner `dist\pointcab_renamer\` + +### Manuell + +1. Öffnen Sie eine Eingabeaufforderung +2. Navigieren Sie zum Quellcode-Ordner +3. Führen Sie PyInstaller aus: + ```cmd + pyinstaller --onefile --windowed --name "PointCab_Renamer" ^
--add-data "cluster_cleanup.txt;." ^
pointcab_renamer.py + ``` +4. Die `.exe` befindet sich in `dist\PointCab_Renamer.exe` + +### PyInstaller-Optionen erklärt + +| Option | Beschreibung | +|--------|-------------| +| `--onefile` | Alles in eine einzige .exe packen | +| `--windowed` | Kein Konsolenfenster anzeigen | +| `--name` | Name der Ausgabedatei | +| `--add-data` | Zusätzliche Dateien einbinden | +| `--icon` | (Optional) Icon-Datei (.ico) | + +### Bekannte Probleme unter Windows + +**Problem:** Antivirus blockiert die .exe + +**Lösung:** Die erstellte .exe als Ausnahme hinzufügen oder signieren. + +**Problem:** "DLL nicht gefunden" + +**Lösung:** Visual C++ Redistributable installieren. + +--- + +## Ubuntu-Build (Binary) + +### Automatisch (empfohlen) + +1. Öffnen Sie ein Terminal +2. Navigieren Sie zum Projektordner: + ```bash + cd /pfad/zum/pointcab_renamer + ``` +3. Machen Sie das Build-Skript ausführbar und führen Sie es aus: + ```bash + chmod +x build_linux.sh + ./build_linux.sh + ``` +4. Das fertige Binary finden Sie im Ordner `dist/` + +### Manuell + +1. Öffnen Sie ein Terminal +2. Navigieren Sie zum Quellcode-Ordner +3. Führen Sie PyInstaller aus: + ```bash + pyinstaller --onefile --name "pointcab_renamer" \ + --add-data "cluster_cleanup.txt:." \ + pointcab_renamer.py + ``` +4. Das Binary befindet sich in `dist/pointcab_renamer` +5. Machen Sie es ausführbar: + ```bash + chmod +x dist/pointcab_renamer + ``` + +### Bekannte Probleme unter Ubuntu + +**Problem:** "No display name and no $DISPLAY environment variable" + +**Lösung:** Das Binary muss in einer grafischen Umgebung gestartet werden, nicht über SSH. + +**Problem:** "_tkinter not found" + +**Lösung:** `sudo apt install python3-tk` + +--- + +## Cross-Compilation: Windows .exe unter Linux erstellen + +Es gibt mehrere Möglichkeiten, eine Windows .exe unter Linux zu erstellen, ohne Windows zu installieren. + +### Methode 1: Docker (Empfohlen) + +Die Docker-Methode ist die zuverlässigste und reproduzierbarste Option. + +#### Voraussetzungen + +1. **Docker installieren:** + ```bash + sudo apt update + sudo apt install docker.io + sudo systemctl start docker + sudo systemctl enable docker + ``` + +2. **Benutzer zur docker-Gruppe hinzufügen:** + ```bash + sudo usermod -aG docker $USER + # Danach neu einloggen oder: + newgrp docker + ``` + +3. **Docker-Installation testen:** + ```bash + docker run hello-world + ``` + +#### Verwendung + +1. Navigieren Sie zum Projektordner: + ```bash + cd /pfad/zum/pointcab_renamer + ``` + +2. Führen Sie das Build-Skript aus: + ```bash + ./build_windows_on_linux.sh + ``` + +3. Die fertige `.exe` befindet sich in `dist/PointCab_Renamer.exe` + +#### Was das Skript macht + +1. Prüft Docker-Installation und -Status +2. Lädt das `cdrx/pyinstaller-windows` Docker-Image (beim ersten Mal) +3. Startet einen Container mit Windows-Umgebung +4. Führt PyInstaller im Container aus +5. Kopiert die .exe und Zusatzdateien nach `dist/` + +#### Vorteile der Docker-Methode + +- ✅ Zuverlässig und reproduzierbar +- ✅ Isolierte Build-Umgebung +- ✅ Keine manuelle Windows-Python-Installation +- ✅ Gleiche Ergebnisse wie auf echtem Windows +- ✅ Einfach in CI/CD-Pipelines integrierbar + +#### Troubleshooting Docker + +**Problem:** "Permission denied" beim Docker-Aufruf + +**Lösung:** +```bash +sudo usermod -aG docker $USER +# Neu einloggen erforderlich! +``` + +**Problem:** Docker-Image-Download schlägt fehl + +**Lösung:** Proxy-Einstellungen prüfen oder manuell herunterladen: +```bash +docker pull cdrx/pyinstaller-windows:python3 +``` + +**Problem:** Container startet nicht + +**Lösung:** Docker-Daemon prüfen: +```bash +sudo systemctl status docker +sudo systemctl restart docker +``` + +--- + +### Methode 2: Wine (Fallback) + +Die Wine-Methode ist weniger zuverlässig, kann aber ohne Docker verwendet werden. + +#### Voraussetzungen + +1. **Wine installieren:** + ```bash + sudo dpkg --add-architecture i386 + sudo apt update + sudo apt install wine64 wine32 + ``` + +2. **Installation prüfen:** + ```bash + wine --version + ``` + +#### Verwendung + +1. Navigieren Sie zum Projektordner: + ```bash + cd /pfad/zum/pointcab_renamer + ``` + +2. Führen Sie das Build-Skript aus: + ```bash + ./build_windows_wine.sh + ``` + +3. Das Skript installiert automatisch: + - Windows-Python in Wine + - PyInstaller + +#### Einschränkungen der Wine-Methode + +- ⚠️ Nicht alle Windows-Funktionen werden unterstützt +- ⚠️ Kann bei komplexen Abhängigkeiten fehlschlagen +- ⚠️ Langsamerer Build-Prozess +- ⚠️ Ergebnisse können von echter Windows-Build abweichen + +--- + +### Methode 3: GitHub Actions (Automatisiert) + +Für regelmäßige Builds können Sie GitHub Actions verwenden. + +Erstellen Sie `.github/workflows/build.yml`: + +```yaml +name: Build Windows Executable + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + build-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: pip install pyinstaller + + - name: Build executable + run: | + pyinstaller --onefile --windowed --name "PointCab_Renamer" ` + --add-data "cluster_cleanup.txt;." ` + --add-data "BENUTZERHANDBUCH.md;." ` + pointcab_renamer.py + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: PointCab_Renamer_Windows + path: | + dist/PointCab_Renamer.exe + cluster_cleanup.txt + BENUTZERHANDBUCH.md +``` + +--- + +### Vergleich der Cross-Compilation-Methoden + +| Methode | Zuverlässigkeit | Geschwindigkeit | Aufwand | +|---------|-----------------|-----------------|---------| +| Docker | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | Niedrig | +| Wine | ⭐⭐ | ⭐⭐ | Mittel | +| GitHub Actions | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Niedrig | +| Echtes Windows | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Hoch (VM) | + +**Empfehlung:** Verwenden Sie die Docker-Methode für lokale Builds und GitHub Actions für automatisierte Release-Builds. + +--- + +## Testen der Executables + +### Windows-Test + +1. Kopieren Sie die `.exe` und `cluster_cleanup.txt` in einen Testordner +2. Doppelklicken Sie auf die `.exe` +3. Das Hauptmenü sollte erscheinen +4. Testen Sie alle drei Modi mit einem Testprojekt + +### Ubuntu-Test + +1. Kopieren Sie das Binary und `cluster_cleanup.txt` in einen Testordner +2. Starten Sie das Programm: + ```bash + ./pointcab_renamer + ``` +3. Das Hauptmenü sollte erscheinen +4. Testen Sie alle drei Modi mit einem Testprojekt + +### Checkliste für Tests + +- [ ] Programm startet ohne Fehler +- [ ] Hauptmenü wird angezeigt +- [ ] LSDX-Datei kann ausgewählt werden +- [ ] PointCloud-Ordner wird erkannt +- [ ] Vorschau wird korrekt angezeigt +- [ ] Umbenennung funktioniert +- [ ] LSDX wird aktualisiert +- [ ] Log-Datei wird erstellt +- [ ] Batch-Modus funktioniert +- [ ] Merger funktioniert + +--- + +## Distribution an Mitarbeiter + +### Bereitstellung + +1. **Für Windows:** + - Kopieren Sie diese Dateien in einen Ordner: + - `PointCab_Renamer.exe` + - `cluster_cleanup.txt` + - `BENUTZERHANDBUCH.md` (oder als PDF) + - Erstellen Sie ein ZIP-Archiv + - Verteilen Sie über Netzlaufwerk oder E-Mail + +2. **Für Ubuntu:** + - Kopieren Sie diese Dateien in einen Ordner: + - `pointcab_renamer` + - `cluster_cleanup.txt` + - `BENUTZERHANDBUCH.md` + - Erstellen Sie ein tar.gz-Archiv: + ```bash + tar -czvf pointcab_renamer_linux.tar.gz pointcab_renamer cluster_cleanup.txt BENUTZERHANDBUCH.md + ``` + - Verteilen Sie über Netzlaufwerk + +### Empfohlene Ordnerstruktur für Mitarbeiter + +``` +PointCab_Renamer/ +├── PointCab_Renamer.exe (oder pointcab_renamer für Linux) +├── cluster_cleanup.txt +├── BENUTZERHANDBUCH.md +└── logs/ (wird automatisch erstellt) +``` + +### Updates verteilen + +1. Erstellen Sie die neue Executable +2. Informieren Sie die Mitarbeiter über Änderungen (CHANGELOG) +3. Mitarbeiter ersetzen die alte .exe durch die neue +4. `cluster_cleanup.txt` kann beibehalten werden (falls angepasst) + +--- + +## Troubleshooting beim Build + +### "ModuleNotFoundError" + +**Lösung:** Fehlende Module installieren: +```bash +pip install +``` + +### "Hidden import not found" + +**Lösung:** Hidden imports hinzufügen: +```bash +pyinstaller --hidden-import= ... +``` + +### "Executable zu groß" (>100MB) + +**Lösung:** UPX-Kompression aktivieren: +```bash +pip install upx +pyinstaller --onefile --upx-dir=/pfad/zu/upx ... +``` + +### "tkinter funktioniert nicht" + +**Windows:** tkinter ist normalerweise in Python enthalten. Reinstallieren Sie Python mit der "tcl/tk" Option. + +**Ubuntu:** Installieren Sie python3-tk: +```bash +sudo apt install python3-tk +``` + +--- + +## Versionskontrolle + +Bei jeder neuen Version: + +1. Version im Quellcode aktualisieren (`VERSION = "4.2"` etc.) +2. CHANGELOG.md aktualisieren +3. Neue Builds für Windows und Ubuntu erstellen +4. Builds testen +5. Im Git-Repository taggen: + ```bash + git tag -a v4.2 -m "Version 4.2" + git push origin v4.2 + ``` + +--- + +--- + +## Troubleshooting: Docker-Probleme + +### Docker ist nicht installiert + +**Ubuntu/Debian:** +```bash +sudo apt update +sudo apt install docker.io +sudo systemctl start docker +sudo systemctl enable docker +sudo usermod -aG docker $USER +# Dann neu einloggen oder: newgrp docker +``` + +**Fedora/RHEL:** +```bash +sudo dnf install docker +sudo systemctl start docker +``` + +### Docker-Daemon startet nicht + +**Symptom:** `Cannot connect to the Docker daemon` + +**Lösungen:** + +1. **Service starten:** + ```bash + sudo systemctl start docker + ``` + +2. **Status prüfen:** + ```bash + sudo systemctl status docker + ``` + +3. **Logs prüfen:** + ```bash + sudo journalctl -u docker.service + ``` + +### Berechtigung verweigert + +**Symptom:** `permission denied while trying to connect to the Docker daemon` + +**Lösung:** Benutzer zur Docker-Gruppe hinzufügen: +```bash +sudo usermod -aG docker $USER +# Danach neu einloggen +``` + +Oder mit sudo ausführen: +```bash +sudo ./build_windows_on_linux.sh +``` + +### Docker in Container-Umgebung (Docker-in-Docker) + +**Problem:** Docker kann nicht in unprivilegierten Containern laufen. + +**Lösungen:** + +1. **Wine-Alternative verwenden:** + ```bash + ./build_windows_wine.sh + ``` + +2. **Auf Host-System bauen** + +3. **GitHub Actions nutzen** (siehe `.github/workflows/`) + +4. **Container mit `--privileged` starten** (nicht empfohlen für Produktion) + +### WSL2 unter Windows + +**Problem:** Docker-Befehle schlagen in WSL2 fehl. + +**Lösung:** +1. Docker Desktop für Windows installieren +2. In Docker Desktop: Settings → Resources → WSL Integration aktivieren +3. WSL-Distribution auswählen + +### Docker-Image Download schlägt fehl + +**Symptom:** `Error pulling image` oder Timeout + +**Lösungen:** + +1. **Internetverbindung prüfen** + +2. **Proxy konfigurieren:** + ```bash + export HTTP_PROXY=http://proxy:port + export HTTPS_PROXY=http://proxy:port + ``` + +3. **Manueller Download:** + ```bash + sudo docker pull cdrx/pyinstaller-windows:python3 + ``` + +--- + +## Vergleich der Build-Methoden + +| Methode | Plattform | Vorteile | Nachteile | +|---------|-----------|----------|-----------| +| `build_windows.bat` | Windows | Nativ, zuverlässig | Braucht Windows | +| `build_windows_on_linux.sh` | Linux + Docker | Cross-compilation | Docker erforderlich | +| `build_windows_wine.sh` | Linux + Wine | Kein Docker nötig | Weniger zuverlässig | +| GitHub Actions | Cloud | Automatisiert | Braucht GitHub-Repo | + +**Empfehlung:** Für zuverlässige Windows-Builds verwenden Sie: +1. **Native Windows** (build_windows.bat) - Am zuverlässigsten +2. **Docker auf Linux** (build_windows_on_linux.sh) - Gut für CI/CD +3. **GitHub Actions** - Automatisiert bei jedem Push/Release + +--- + +*PointCab Renamer Deployment Guide v4.1.2 - © 2026* diff --git a/release/PointCab_Renamer_v4.2.1/GIT_SETUP.md b/release/PointCab_Renamer_v4.2.1/GIT_SETUP.md new file mode 100644 index 0000000..ba79337 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/GIT_SETUP.md @@ -0,0 +1,107 @@ +# Git-Repository Setup für PointCab Renamer + +## Voraussetzungen + +- Git installiert +- Zugang zu Gitea/GitHub Repository + +## Lokales Repository initialisieren + +Das Repository wurde bereits initialisiert. Falls Sie ein neues Repository erstellen möchten: + +```bash +cd pointcab_renamer +git init +git add . +git commit -m "Initial commit: PointCab Renamer v4.2" +``` + +## Zu Gitea pushen + +1. **Repository auf Gitea erstellen** (falls noch nicht geschehen) + - Loggen Sie sich bei Gitea ein + - Erstellen Sie ein neues Repository (z.B. `pointcab_renamer`) + - Kopieren Sie die Repository-URL + +2. **Remote hinzufügen und pushen:** + +```bash +# Remote hinzufügen +git remote add origin https://gitea.example.com/username/pointcab_renamer.git + +# Oder für SSH: +git remote add origin git@gitea.example.com:username/pointcab_renamer.git + +# Push zum Remote +git push -u origin main +``` + +## Zu GitHub pushen + +```bash +# Remote hinzufügen +git remote add origin https://github.com/username/pointcab_renamer.git + +# Oder für SSH: +git remote add origin git@github.com:username/pointcab_renamer.git + +# Push zum Remote +git push -u origin main +``` + +## Änderungen pushen + +Nach dem initialen Push: + +```bash +# Änderungen hinzufügen +git add . + +# Commit erstellen +git commit -m "Beschreibung der Änderungen" + +# Pushen +git push +``` + +## Branching-Strategie + +- `main` - Stabiler Release-Branch +- `develop` - Entwicklungs-Branch +- `feature/*` - Feature-Branches +- `bugfix/*` - Bugfix-Branches + +## Releases erstellen + +```bash +# Tag für Release erstellen +git tag -a v4.2 -m "Release v4.2 - Bugfixes und Parser-Verbesserungen" + +# Tag pushen +git push origin v4.2 +``` + +## Häufige Befehle + +```bash +# Status anzeigen +git status + +# Log anzeigen +git log --oneline + +# Änderungen abrufen +git pull + +# Branch wechseln +git checkout branch-name + +# Neuen Branch erstellen +git checkout -b neuer-branch +``` + +## Hinweise + +- Die `.gitignore` ignoriert Build-Artefakte, Logs und temporäre Dateien +- Bei Konflikten: `git pull --rebase` verwenden +- Regelmäßig pushen, um Datenverlust zu vermeiden diff --git a/release/PointCab_Renamer_v4.2.1/INSTALLATION.txt b/release/PointCab_Renamer_v4.2.1/INSTALLATION.txt new file mode 100644 index 0000000..86943d7 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/INSTALLATION.txt @@ -0,0 +1,73 @@ +===================================================== + PointCab Renamer v4.1 - Schnellstart-Anleitung +===================================================== + +INHALT DES ARCHIVS: +------------------- +- pointcab_renamer.py - Hauptprogramm (Quellcode) +- cluster_cleanup.txt - Konfigurationsdatei +- BENUTZERHANDBUCH.md - Ausführliche Anleitung +- DEPLOYMENT.md - Anleitung zum Erstellen von .exe/Binary +- README.md - Projektübersicht +- CHANGELOG.md - Versionsänderungen +- build_windows.bat - Build-Skript für Windows +- build_linux.sh - Build-Skript für Linux +- requirements.txt - Python-Abhängigkeiten +- LICENSE.txt - Lizenzinformationen +- VERSION.txt - Versionsinformationen + + +SCHNELLSTART - WINDOWS: +----------------------- +1. Entpacken Sie das Archiv in einen beliebigen Ordner +2. Option A - Mit Python: + - Python 3.8+ installieren (python.org) + - Doppelklick auf pointcab_renamer.py + + Option B - Als .exe erstellen: + - Doppelklick auf build_windows.bat + - Fertige .exe liegt in dist/ + + +SCHNELLSTART - LINUX/UBUNTU: +---------------------------- +1. Entpacken Sie das Archiv: + unzip pointcab_renamer_v4.1_release.zip + cd pointcab_renamer_release + +2. Option A - Mit Python: + sudo apt install python3 python3-tk + python3 pointcab_renamer.py + + Option B - Als Binary erstellen: + chmod +x build_linux.sh + ./build_linux.sh + ./dist/pointcab_renamer + + +ERSTE SCHRITTE: +--------------- +1. Starten Sie das Programm +2. Wählen Sie einen Modus: + - Einzelprojekt: Ein PointCab-Projekt umbenennen + - Batch: Mehrere Projekte auf einmal + - Merger: Projekte zusammenführen +3. Folgen Sie den Anweisungen auf dem Bildschirm + + +WICHTIGE HINWEISE: +------------------ +- Die Datei cluster_cleanup.txt muss im selben Ordner + wie das Programm liegen +- Vor dem Umbenennen immer ein Backup erstellen! +- Bei Problemen: BENUTZERHANDBUCH.md lesen + + +SUPPORT: +-------- +Bei Fragen wenden Sie sich an die IT-Abteilung. + + +===================================================== + Version 4.1 | Januar 2026 +===================================================== diff --git a/release/PointCab_Renamer_v4.2.1/LICENSE.txt b/release/PointCab_Renamer_v4.2.1/LICENSE.txt new file mode 100644 index 0000000..f588611 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/LICENSE.txt @@ -0,0 +1,32 @@ +PointCab Renamer - Lizenzvereinbarung +===================================== + +Copyright (c) 2026 - Alle Rechte vorbehalten + +NUTZUNGSBEDINGUNGEN: + +1. INTERNE NUTZUNG + Diese Software ist ausschließlich für den internen Gebrauch + innerhalb des Unternehmens bestimmt. + +2. WEITERGABE + Die Weitergabe an Dritte außerhalb des Unternehmens ist + ohne ausdrückliche schriftliche Genehmigung untersagt. + +3. VERÄNDERUNGEN + Änderungen am Quellcode sind nur mit Rücksprache mit der + IT-Abteilung gestattet. + +4. GEWÄHRLEISTUNG + Die Software wird "wie besehen" ohne jegliche Gewährleistung + bereitgestellt. Der Autor haftet nicht für Schäden, die durch + die Nutzung dieser Software entstehen könnten. + +5. SUPPORT + Bei Fragen oder Problemen wenden Sie sich bitte an die + IT-Abteilung. + +--- + +Diese Lizenz gilt für alle Versionen der Software, sofern +nicht anders angegeben. diff --git a/release/PointCab_Renamer_v4.2.1/README.md b/release/PointCab_Renamer_v4.2.1/README.md new file mode 100644 index 0000000..1af9ffe --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/README.md @@ -0,0 +1,276 @@ +# PointCab Projekt Umbenenner v4.1.1 + +Ein GUI-Tool zum Umbenennen von Scans in PointCab-Projekten und zum Zusammenführen mehrerer Projekte. + +## Funktionen + +### 1. 📁 Einzelprojekt bearbeiten +- Einzelnes PointCab-Projekt auswählen und Scans umbenennen +- Vollständige Scan-Namen: `1.lsd → Projektname_01.lsd` +- Clustername-Bereinigung über Konfigurationsdatei +- Automatisches Backup der LSDX-Datei + +### 2. 📂 Batch Renamer +- Mehrere Projekte in einem Verzeichnis automatisch verarbeiten +- Fortschrittsanzeige und detailliertes Logging +- Fehlertoleranz: Bei Fehler wird mit nächstem Projekt fortgefahren + +### 3. 🔀 Projektmerger (verbessert in v4.1) +- Mehrere PointCab-Projekte in ein Stammprojekt zusammenführen +- Zwei Modi: Einzelprojekt oder Batch-Merge +- **NEU**: Intelligente Cluster-Duplikat-Erkennung +- Intelligente Namenskonflikt-Behandlung +- Vollständige LSDX-Zusammenführung mit detailliertem Logging + +## Installation + +### Voraussetzungen +- Python 3.8 oder höher +- Tkinter (normalerweise in Python enthalten) + +### Ausführen +```bash +python pointcab_renamer.py +``` + +## Projektmerger - Detaillierte Dokumentation + +### Konzept +Der Projektmerger ermöglicht das Zusammenführen mehrerer PointCab-Projekte in ein einzelnes Stammprojekt. Dies ist nützlich wenn: +- Mehrere Scan-Sessions zu einem Projekt gehören +- Projekte nachträglich zusammengeführt werden sollen +- Daten aus verschiedenen Quellen konsolidiert werden müssen + +### Modi + +#### Einzelprojekt hinzufügen +1. Stammprojekt (Ziel) auswählen +2. Ein einzelnes Quellprojekt auswählen +3. Vorschau anzeigen +4. Merge durchführen + +#### Batch-Merge +1. Stammprojekt (Ziel) auswählen +2. Hauptverzeichnis mit mehreren Quellprojekten auswählen +3. Alle gefundenen Projekte werden automatisch erkannt +4. Vorschau anzeigen +5. Merge durchführen + +### Merge-Operationen + +Der Projektmerger führt folgende Operationen durch: + +1. **Backup erstellen** + - Vor dem Merge wird ein Backup der Stammprojekt-LSDX erstellt + - Format: `projektname.lsdx.backup_YYYYMMDD_HHMMSS` + +2. **LSD-Dateien kopieren** + - Alle LSD-Dateien aus den Quellprojekten werden in das Stammprojekt kopiert + - Bei Namenskonflikten: Automatische Umbenennung (siehe unten) + +3. **PNG-Dateien kopieren** + - Alle Preview-Bilder werden in den Previews-Ordner des Stammprojekts kopiert + - Bei Namenskonflikten: Automatische Umbenennung + +4. **LSDX zusammenführen** + - **Cluster-Duplikat-Erkennung** (NEU in v4.1): + - Prüft ob Cluster mit gleichem Namen bereits existiert + - Bei Duplikat: Scans werden dem existierenden Cluster zugeordnet + - Bei neuem Cluster: Neuer Cluster wird mit neuer UUID hinzugefügt + - Alle Scan-Elemente werden mit korrekten Parent-Referenzen eingefügt + - UUIDs werden neu generiert um Konflikte zu vermeiden + - FilePath-Referenzen werden bei Umbenennung angepasst + - Detailliertes Logging aller Operationen + +### Namenskonflikt-Behandlung + +Wenn eine Datei im Zielordner bereits existiert: + +``` +Vor Merge: + Stammprojekt/PointCloud/scan_01.lsd (existiert) + Quellprojekt/PointCloud/scan_01.lsd (zu mergen) + +Nach Merge: + Stammprojekt/PointCloud/scan_01.lsd (original) + Stammprojekt/PointCloud/scan_01_merged_1.lsd (aus Quellprojekt) +``` + +Die LSDX-Referenzen werden automatisch aktualisiert: +```xml + +scan_01.lsd + + +scan_01_merged_1.lsd +``` + +### Beispiel: Einzelprojekt-Merge + +``` +Vorher: +├── Stammprojekt/ +│ ├── Stammprojekt_PointCloud/ +│ │ ├── Stammprojekt.lsdx +│ │ ├── 1.lsd +│ │ ├── 2.lsd +│ │ └── Previews/ +│ │ ├── 1.png +│ │ └── 2.png + +├── Quellprojekt/ +│ ├── Quellprojekt_PointCloud/ +│ │ ├── Quellprojekt.lsdx +│ │ ├── 1.lsd +│ │ ├── 2.lsd +│ │ └── Previews/ +│ │ ├── 1.png +│ │ └── 2.png + +Nachher: +├── Stammprojekt/ +│ ├── Stammprojekt_PointCloud/ +│ │ ├── Stammprojekt.lsdx (zusammengeführt) +│ │ ├── Stammprojekt.lsdx.backup_20260114_101500 +│ │ ├── 1.lsd +│ │ ├── 2.lsd +│ │ ├── 1_merged_1.lsd (aus Quellprojekt) +│ │ ├── 2_merged_2.lsd (aus Quellprojekt) +│ │ └── Previews/ +│ │ ├── 1.png +│ │ ├── 2.png +│ │ ├── 1_merged_1.png +│ │ └── 2_merged_2.png +│ └── merge_20260114_101500.log +``` + +### Beispiel: Batch-Merge + +``` +Vorher: +├── Hauptverzeichnis/ +│ ├── Stammprojekt/ +│ │ └── Stammprojekt_PointCloud/ +│ │ ├── Stammprojekt.lsdx +│ │ └── (Scans 1-5) +│ ├── Projekt_A/ +│ │ └── Projekt_A_PointCloud/ +│ │ ├── Projekt_A.lsdx +│ │ └── (Scans 1-3) +│ └── Projekt_B/ +│ └── Projekt_B_PointCloud/ +│ ├── Projekt_B.lsdx +│ └── (Scans 1-4) + +Nach Batch-Merge (Stammprojekt als Ziel, Hauptverzeichnis als Quelle): +├── Stammprojekt/ +│ └── Stammprojekt_PointCloud/ +│ ├── Stammprojekt.lsdx (enthält jetzt 12 Scans) +│ ├── Stammprojekt.lsdx.backup_... +│ └── (alle LSD/PNG-Dateien) +│ └── merge_....log +``` + +### Logging + +Jeder Merge-Vorgang erstellt eine detaillierte Log-Datei: + +- **Einzelprojekt-Merge**: `merge_YYYYMMDD_HHMMSS.log` im Stammprojekt-Verzeichnis +- **Batch-Merge**: Eine Log-Datei pro Merge-Vorgang + +Log-Inhalt: +- Alle kopierten Dateien +- Umbenennungen bei Konflikten +- Aktualisierte LSDX-Referenzen +- Fehler und Warnungen + +### Fehlerbehandlung + +- **Fehlende Dateien**: Werden übersprungen, Warnung im Log +- **Batch-Merge bei Fehler**: Verarbeitung wird mit nächstem Projekt fortgesetzt +- **LSDX-Parsing-Fehler**: Projekt wird übersprungen +- **Backup**: Immer vor Änderungen erstellt + +## Konfiguration + +### cluster_cleanup.txt +Strings die aus dem Clusternamen entfernt werden: +``` +_re +_li +_mi +# Kommentare mit # beginnen +``` + +## Executable erstellen + +### Windows (auf Windows) +```bash +pip install pyinstaller +pyinstaller --onefile --windowed pointcab_renamer.py +``` + +### Linux +```bash +./build_linux.sh +``` + +### Cross-Compilation: Windows .exe unter Linux +Es ist möglich, eine Windows .exe unter Linux zu erstellen. Dazu stehen zwei Methoden zur Verfügung: + +```bash +# Methode 1: Docker (empfohlen) +./build_windows_on_linux.sh + +# Methode 2: Wine (Fallback) +./build_windows_wine.sh +``` + +Die Docker-Methode ist zuverlässiger und wird empfohlen. Für Details siehe [DEPLOYMENT.md](DEPLOYMENT.md). + +**Wichtig:** Die `cluster_cleanup.txt` muss neben der .exe-Datei liegen. + +## Changelog + +### v4.1.1 (2026-01-14) +- **NEU**: Cross-Compilation-Unterstützung für Windows .exe unter Linux + - Docker-basiertes Build-Skript (`build_windows_on_linux.sh`) + - Wine-basiertes Fallback-Skript (`build_windows_wine.sh`) + - GitHub Actions Beispiel-Workflow +- Erweiterte DEPLOYMENT.md-Dokumentation + +### v4.1 (2026-01-14) +- **FIX**: Projektmerger LSDX-Zusammenführung komplett überarbeitet + - Cluster-Duplikat-Erkennung: Verhindert doppelte Cluster bei gleichem Namen + - Scans werden korrekt dem existierenden oder neuen Cluster zugeordnet + - Parent-Referenzen werden jetzt korrekt gesetzt (Cluster→Registration, Scan→Cluster) + - Detailliertes Logging aller Merge-Operationen + - Finale Scan/Cluster-Statistik nach Merge +- LSDX-Struktur im Code dokumentiert + +### v4.0 (2026-01-14) +- **NEU**: Projektmerger-Funktion + - Einzelprojekt- und Batch-Merge-Modi + - Intelligente Namenskonflikt-Behandlung + - Automatische UUID-Neugenerierung + - Vollständige LSDX-Zusammenführung +- Hauptmenü auf 3 Optionen erweitert +- Verbesserte Fehlerbehandlung + +### v3.1 (2026-01-14) +- Full-Scan-Name-Pattern: `1.lsd → Projektname_01.lsd` +- Konsistente Benennung in Dateien und LSDX + +### v3.0 (2026-01-14) +- Batch Renamer hinzugefügt +- Hauptmenü für Modus-Auswahl + +### v2.0 (2026-01-14) +- Clustername-Bereinigung via Konfigurationsdatei + +### v1.0 (2026-01-14) +- Initiale Version + +## Lizenz + +MIT License diff --git a/release/PointCab_Renamer_v4.2.1/VERSION.txt b/release/PointCab_Renamer_v4.2.1/VERSION.txt new file mode 100644 index 0000000..fae6e3d --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/VERSION.txt @@ -0,0 +1 @@ +4.2.1 diff --git a/release/PointCab_Renamer_v4.2.1/build_linux.sh b/release/PointCab_Renamer_v4.2.1/build_linux.sh new file mode 100755 index 0000000..e227275 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/build_linux.sh @@ -0,0 +1,160 @@ +#!/bin/bash +# ============================================ +# PointCab Renamer - Linux Build Script +# Version 4.2.1 +# ============================================ + +set -e # Bei Fehlern abbrechen + +echo "" +echo "===================================" +echo " PointCab Renamer - Linux Build" +echo " Version 4.2.1" +echo "===================================" +echo "" + +# Farben für Ausgabe +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Arbeitsverzeichnis +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo -e "${BLUE}[1/6] Prüfe Voraussetzungen...${NC}" + +# Prüfe ob Python3 installiert ist +if ! command -v python3 &> /dev/null; then + echo -e "${RED}[FEHLER] Python3 ist nicht installiert.${NC}" + echo "" + echo "Installation:" + echo " Ubuntu/Debian: sudo apt install python3 python3-pip python3-tk" + echo " Fedora: sudo dnf install python3 python3-pip python3-tkinter" + exit 1 +fi + +PYTHON_VERSION=$(python3 --version 2>&1) +echo -e "${GREEN}✓ Python3 gefunden: $PYTHON_VERSION${NC}" + +# Prüfe ob tkinter installiert ist +echo -e "${BLUE}[2/6] Prüfe tkinter...${NC}" +if ! python3 -c "import tkinter" 2>/dev/null; then + echo -e "${YELLOW}[WARNUNG] tkinter nicht gefunden.${NC}" + echo "" + echo "Installation von tkinter:" + echo " Ubuntu/Debian: sudo apt install python3-tk" + echo " Fedora: sudo dnf install python3-tkinter" + echo "" + + # Versuche automatische Installation (nur wenn sudo verfügbar) + if command -v apt &> /dev/null && [ -w /etc/apt ]; then + echo "Versuche automatische Installation..." + sudo apt install -y python3-tk || { + echo -e "${RED}[FEHLER] Automatische Installation fehlgeschlagen.${NC}" + echo "Bitte manuell installieren: sudo apt install python3-tk" + exit 1 + } + else + echo -e "${RED}[FEHLER] tkinter muss manuell installiert werden.${NC}" + exit 1 + fi +fi +echo -e "${GREEN}✓ tkinter verfügbar${NC}" + +# Prüfe ob pip installiert ist +echo -e "${BLUE}[3/6] Prüfe pip...${NC}" +if ! command -v pip3 &> /dev/null && ! python3 -m pip --version &> /dev/null; then + echo -e "${YELLOW}[INFO] pip3 nicht gefunden.${NC}" + echo "" + echo "Installation von pip:" + echo " Ubuntu/Debian: sudo apt install python3-pip" + echo " Fedora: sudo dnf install python3-pip" + echo " Oder: python3 -m ensurepip --upgrade" + exit 1 +fi +echo -e "${GREEN}✓ pip verfügbar${NC}" + +# Prüfe ob PyInstaller installiert ist +echo -e "${BLUE}[4/6] Prüfe PyInstaller...${NC}" +if ! python3 -m PyInstaller --version &> /dev/null 2>&1; then + echo -e "${YELLOW}[INFO] PyInstaller nicht gefunden. Installiere...${NC}" + python3 -m pip install --user pyinstaller || { + echo -e "${RED}[FEHLER] PyInstaller konnte nicht installiert werden.${NC}" + echo "Versuche: pip3 install pyinstaller" + exit 1 + } +fi +PYINSTALLER_VERSION=$(python3 -m PyInstaller --version 2>&1) +echo -e "${GREEN}✓ PyInstaller installiert: $PYINSTALLER_VERSION${NC}" + +# Prüfe Projektdateien +echo -e "${BLUE}[5/6] Prüfe Projektdateien...${NC}" +if [ ! -f "pointcab_renamer.py" ]; then + echo -e "${RED}[FEHLER] pointcab_renamer.py nicht gefunden!${NC}" + echo "Bitte führen Sie das Skript im Projektverzeichnis aus." + exit 1 +fi + +if [ ! -f "cluster_cleanup.txt" ]; then + echo -e "${RED}[FEHLER] cluster_cleanup.txt nicht gefunden!${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Projektdateien vorhanden${NC}" + +# Lösche alte Build-Verzeichnisse +echo "" +echo -e "${BLUE}[6/6] Starte Build-Prozess...${NC}" +echo "[INFO] Räume alte Build-Dateien auf..." +rm -rf build dist *.spec 2>/dev/null || true + +echo "[INFO] Erstelle Linux-Binary..." +echo "" + +# PyInstaller ausführen +python3 -m PyInstaller \ + --onefile \ + --name "pointcab_renamer" \ + --add-data "cluster_cleanup.txt:." \ + pointcab_renamer.py + +if [ $? -ne 0 ]; then + echo "" + echo -e "${RED}[FEHLER] Build fehlgeschlagen!${NC}" + echo "" + echo "Mögliche Ursachen:" + echo " - PyInstaller-Version inkompatibel" + echo " - Fehlende Abhängigkeiten" + echo "" + echo "Versuche: pip3 install --upgrade pyinstaller" + exit 1 +fi + +echo "" +echo -e "${GREEN}[INFO] Build erfolgreich!${NC}" +echo "" + +# Kopiere notwendige Dateien in dist-Ordner +echo "[INFO] Kopiere zusätzliche Dateien..." +cp cluster_cleanup.txt dist/ +[ -f BENUTZERHANDBUCH.md ] && cp BENUTZERHANDBUCH.md dist/ + +# Mache das Binary ausführbar +chmod +x dist/pointcab_renamer + +# Zeige Ergebnis +echo "" +echo -e "${GREEN}===================================${NC}" +echo -e "${GREEN} BUILD ERFOLGREICH!${NC}" +echo -e "${GREEN}===================================${NC}" +echo "" +echo "Erstellte Dateien:" +ls -lh dist/ +echo "" +echo "Verwendung:" +echo " cd dist && ./pointcab_renamer" +echo "" +echo "Für Distribution alle Dateien aus dist/ kopieren." +echo "" diff --git a/release/PointCab_Renamer_v4.2.1/build_windows.bat b/release/PointCab_Renamer_v4.2.1/build_windows.bat new file mode 100644 index 0000000..05139b6 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/build_windows.bat @@ -0,0 +1,119 @@ +@echo off +REM ============================================ +REM PointCab Renamer - Windows Build Script v4.2 +REM ============================================ +REM Verwendet den Python Launcher (py) für bessere Kompatibilität +REM +REM Voraussetzungen: +REM - Python 3.8+ mit py Launcher installiert +REM - PyInstaller (wird bei Bedarf installiert) +REM +REM Verwendung: +REM 1. Öffnen Sie die Eingabeaufforderung (cmd) +REM 2. Navigieren Sie zum Projektordner +REM 3. Führen Sie: build_windows.bat aus +REM ============================================ + +setlocal enabledelayedexpansion + +echo. +echo ============================================ +echo PointCab Renamer - Windows Build v4.2 +echo ============================================ +echo. + +REM Prüfe Python Installation +echo [1/5] Prüfe Python Installation... +py --version >nul 2>&1 +if errorlevel 1 ( + echo. + echo FEHLER: Python wurde nicht gefunden! + echo. + echo Bitte installieren Sie Python von: + echo https://www.python.org/downloads/ + echo. + echo Stellen Sie sicher, dass bei der Installation + echo "Add Python to PATH" aktiviert ist. + echo. + pause + exit /b 1 +) + +for /f "tokens=2" %%v in ('py --version 2^>^&1') do set PYTHON_VERSION=%%v +echo Python %PYTHON_VERSION% gefunden. + +REM Prüfe/Installiere PyInstaller +echo. +echo [2/5] Prüfe PyInstaller... +py -m PyInstaller --version >nul 2>&1 +if errorlevel 1 ( + echo PyInstaller nicht gefunden. Installiere... + py -m pip install pyinstaller + if errorlevel 1 ( + echo. + echo FEHLER: PyInstaller konnte nicht installiert werden! + echo Bitte führen Sie manuell aus: + echo py -m pip install pyinstaller + echo. + pause + exit /b 1 + ) +) +for /f "tokens=*" %%v in ('py -m PyInstaller --version 2^>^&1') do set PYINSTALLER_VERSION=%%v +echo PyInstaller %PYINSTALLER_VERSION% gefunden. + +REM Bereinige alte Builds +echo. +echo [3/5] Bereinige alte Build-Dateien... +if exist build rmdir /s /q build +if exist dist rmdir /s /q dist +if exist *.spec del /f /q *.spec +echo Alte Dateien entfernt. + +REM Erstelle Executable +echo. +echo [4/5] Erstelle Windows Executable... +echo Dies kann einige Minuten dauern... +echo. + +py -m PyInstaller --onefile --windowed --name "PointCab_Renamer" ^ + --add-data "cluster_cleanup.txt;." ^ + --add-data "BENUTZERHANDBUCH.md;." ^ + pointcab_renamer.py + +if errorlevel 1 ( + echo. + echo FEHLER: Build fehlgeschlagen! + echo Bitte prüfen Sie die Fehlermeldungen oben. + echo. + pause + exit /b 1 +) + +REM Kopiere zusätzliche Dateien +echo. +echo [5/5] Kopiere zusätzliche Dateien... +copy cluster_cleanup.txt dist\ >nul 2>&1 +copy BENUTZERHANDBUCH.md dist\ >nul 2>&1 +copy README.md dist\ >nul 2>&1 +echo Dateien kopiert. + +REM Erfolgsmeldung +echo. +echo ============================================ +echo BUILD ERFOLGREICH! +echo ============================================ +echo. +echo Die Executable befindet sich in: +echo dist\PointCab_Renamer.exe +echo. +echo Zusätzliche Dateien in dist\: +echo - cluster_cleanup.txt +echo - BENUTZERHANDBUCH.md +echo - README.md +echo. +echo Hinweis: Die cluster_cleanup.txt muss neben +echo der .exe Datei liegen! +echo. +echo ============================================ +pause diff --git a/release/PointCab_Renamer_v4.2.1/build_windows_on_linux.sh b/release/PointCab_Renamer_v4.2.1/build_windows_on_linux.sh new file mode 100755 index 0000000..1e93536 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/build_windows_on_linux.sh @@ -0,0 +1,278 @@ +#!/bin/bash +# ============================================================================ +# PointCab Renamer - Windows Build unter Linux (Docker-Methode) +# Version: 4.1.2 +# ============================================================================ +# +# Dieses Skript erstellt eine Windows .exe unter Linux mittels Docker. +# Es verwendet das cdrx/pyinstaller-windows Image für zuverlässige Builds. +# +# VORAUSSETZUNGEN: +# - Docker muss installiert sein (wird bei Bedarf mit sudo gestartet) +# - Internet-Verbindung für den ersten Docker-Image-Download +# +# VERWENDUNG: +# ./build_windows_on_linux.sh +# +# TROUBLESHOOTING: +# Falls Docker nicht startet, prüfen Sie: +# - sudo systemctl start docker +# - Benutzer zur docker-Gruppe hinzufügen: sudo usermod -aG docker $USER +# - In Container-Umgebungen (z.B. Docker-in-Docker): --privileged Flag nötig +# +# ============================================================================ + +set -e # Bei Fehlern abbrechen + +# Farben für Ausgabe +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Banner +echo -e "${BLUE}" +echo "============================================================================" +echo " PointCab Renamer - Windows Cross-Compilation unter Linux" +echo " Version 4.1.2" +echo "============================================================================" +echo -e "${NC}" + +# Hilfsfunktion: Docker-Befehl mit oder ohne sudo +docker_cmd() { + if docker "$@" 2>/dev/null; then + return 0 + elif sudo docker "$@" 2>/dev/null; then + USE_SUDO=1 + return 0 + else + return 1 + fi +} + +run_docker() { + if [ "$USE_SUDO" = "1" ]; then + sudo docker "$@" + else + docker "$@" + fi +} + +# [1/7] Prüfe ob Docker installiert ist +echo -e "${YELLOW}[1/7] Prüfe Docker-Installation...${NC}" +if ! command -v docker &> /dev/null; then + echo -e "${RED}FEHLER: Docker ist nicht installiert!${NC}" + echo "" + echo "Installiere Docker mit:" + echo " Ubuntu/Debian:" + echo " sudo apt update" + echo " sudo apt install docker.io" + echo " sudo systemctl start docker" + echo " sudo systemctl enable docker" + echo " sudo usermod -aG docker \$USER" + echo " # Danach neu einloggen oder: newgrp docker" + echo "" + echo " Fedora/RHEL:" + echo " sudo dnf install docker" + echo " sudo systemctl start docker" + echo "" + echo "Alternativ: Verwende ./build_windows_wine.sh (Wine-basiert)" + exit 1 +fi +echo -e "${GREEN}✓ Docker ist installiert${NC}" + +# [2/7] Prüfe ob Docker-Daemon läuft, versuche Start mit sudo falls nötig +echo -e "${YELLOW}[2/7] Prüfe Docker-Daemon...${NC}" +USE_SUDO=0 + +if docker info &> /dev/null; then + echo -e "${GREEN}✓ Docker-Daemon läuft (ohne sudo)${NC}" +elif sudo docker info &> /dev/null; then + USE_SUDO=1 + echo -e "${GREEN}✓ Docker-Daemon läuft (mit sudo)${NC}" +else + # Versuche Docker zu starten + echo -e "${YELLOW}Docker-Daemon läuft nicht. Versuche zu starten...${NC}" + + # Versuche systemd + if command -v systemctl &> /dev/null; then + sudo systemctl start docker 2>/dev/null && sleep 2 + fi + + # Prüfe erneut + if docker info &> /dev/null; then + echo -e "${GREEN}✓ Docker-Daemon erfolgreich gestartet${NC}" + elif sudo docker info &> /dev/null; then + USE_SUDO=1 + echo -e "${GREEN}✓ Docker-Daemon erfolgreich gestartet (mit sudo)${NC}" + else + echo -e "${RED}FEHLER: Docker-Daemon konnte nicht gestartet werden!${NC}" + echo "" + echo "Mögliche Ursachen und Lösungen:" + echo "" + echo "1. Docker-Service nicht gestartet:" + echo " sudo systemctl start docker" + echo "" + echo "2. Berechtigungsproblem:" + echo " sudo usermod -aG docker \$USER" + echo " # Dann neu einloggen" + echo "" + echo "3. In Container-Umgebung (Docker-in-Docker):" + echo " Docker kann nicht in unprivilegierten Containern laufen." + echo " Nutze stattdessen: ./build_windows_wine.sh" + echo " Oder: Baue auf einem System mit nativem Docker." + echo "" + echo "4. WSL2 unter Windows:" + echo " Starte Docker Desktop und aktiviere WSL2-Integration." + echo "" + echo "Alternative Build-Methoden:" + echo " - ./build_windows_wine.sh (Wine-basiert)" + echo " - GitHub Actions (siehe .github/workflows/)" + echo " - Natives Windows: build_windows.bat" + exit 1 + fi +fi + +# Arbeitsverzeichnis +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# [3/7] Prüfe ob notwendige Dateien existieren +echo -e "${YELLOW}[3/7] Prüfe Projektdateien...${NC}" +if [ ! -f "pointcab_renamer.py" ]; then + echo -e "${RED}FEHLER: pointcab_renamer.py nicht gefunden!${NC}" + echo "Stelle sicher, dass du dich im richtigen Verzeichnis befindest." + exit 1 +fi + +if [ ! -f "cluster_cleanup.txt" ]; then + echo -e "${RED}FEHLER: cluster_cleanup.txt nicht gefunden!${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Alle Projektdateien vorhanden${NC}" + +# [4/7] Aufräumen +echo -e "${YELLOW}[4/7] Räume alte Build-Artefakte auf...${NC}" +rm -rf build/ dist/ *.spec 2>/dev/null || true +mkdir -p dist +echo -e "${GREEN}✓ Build-Verzeichnisse bereinigt${NC}" + +# Docker Image +DOCKER_IMAGE="cdrx/pyinstaller-windows:python3" + +# [5/7] Prüfe/Lade Docker Image +echo -e "${YELLOW}[5/7] Prüfe Docker Image...${NC}" +echo " Image: $DOCKER_IMAGE" +echo " (Beim ersten Mal wird ~1.5 GB heruntergeladen)" + +if ! run_docker image inspect "$DOCKER_IMAGE" &> /dev/null; then + echo "" + echo "Lade Docker Image herunter..." + run_docker pull "$DOCKER_IMAGE" +fi +echo -e "${GREEN}✓ Docker Image bereit${NC}" + +# [6/7] Build durchführen +echo -e "${YELLOW}[6/7] Erstelle Windows .exe mit PyInstaller...${NC}" +echo " Dies kann 2-5 Minuten dauern..." +echo "" + +# Erstelle temporäres Build-Skript für Docker-Container +BUILD_SCRIPT=$(mktemp) +cat > "$BUILD_SCRIPT" << 'DOCKERSCRIPT' +#!/bin/bash +set -e +cd /src + +echo "=== Docker Container gestartet ===" +echo "Python-Version: $(python --version)" +echo "PyInstaller-Version: $(pip show pyinstaller | grep Version)" +echo "" + +# Installiere requirements falls vorhanden +if [ -f "requirements.txt" ]; then + echo "Installiere requirements..." + pip install -r requirements.txt --quiet +fi + +echo "Starte PyInstaller Build..." + +# PyInstaller Build +pyinstaller --onefile \ + --windowed \ + --name "PointCab_Renamer" \ + --add-data "cluster_cleanup.txt;." \ + --add-data "BENUTZERHANDBUCH.md;." \ + --icon="NONE" \ + --clean \ + pointcab_renamer.py + +echo "" +echo "=== Build im Container abgeschlossen ===" +DOCKERSCRIPT + +chmod +x "$BUILD_SCRIPT" + +# Docker Container ausführen +if [ "$USE_SUDO" = "1" ]; then + sudo docker run --rm \ + -v "$SCRIPT_DIR":/src \ + -v "$BUILD_SCRIPT":/docker_build.sh:ro \ + "$DOCKER_IMAGE" \ + bash /docker_build.sh +else + docker run --rm \ + -v "$SCRIPT_DIR":/src \ + -v "$BUILD_SCRIPT":/docker_build.sh:ro \ + "$DOCKER_IMAGE" \ + bash /docker_build.sh +fi + +# Aufräumen +rm -f "$BUILD_SCRIPT" + +# [7/7] Prüfe Ergebnis +echo "" +echo -e "${YELLOW}[7/7] Verifiziere Build-Ergebnis...${NC}" + +if [ -f "dist/PointCab_Renamer.exe" ]; then + echo -e "${GREEN}✓ Windows .exe erfolgreich erstellt!${NC}" + + # Kopiere zusätzliche Dateien + cp cluster_cleanup.txt dist/ + [ -f "BENUTZERHANDBUCH.md" ] && cp BENUTZERHANDBUCH.md dist/ + [ -f "BENUTZERHANDBUCH.pdf" ] && cp BENUTZERHANDBUCH.pdf dist/ + [ -f "README.md" ] && cp README.md dist/ + [ -f "LICENSE.txt" ] && cp LICENSE.txt dist/ + + # Zeige Ergebnis + echo "" + echo -e "${BLUE}============================================================================${NC}" + echo -e "${GREEN}BUILD ERFOLGREICH!${NC}" + echo -e "${BLUE}============================================================================${NC}" + echo "" + echo "Erstellte Dateien in dist/:" + ls -lh dist/ + echo "" + EXE_SIZE=$(du -h "dist/PointCab_Renamer.exe" | cut -f1) + echo "Executable-Größe: $EXE_SIZE" + echo "" + echo "Pfad: $SCRIPT_DIR/dist/" + echo "" + echo -e "${YELLOW}WICHTIG:${NC}" + echo " 1. Teste die .exe auf einem echten Windows-System!" + echo " 2. Stelle sicher, dass cluster_cleanup.txt im gleichen" + echo " Ordner wie die .exe liegt." + echo "" +else + echo -e "${RED}FEHLER: Build fehlgeschlagen!${NC}" + echo "" + echo "Die Datei dist/PointCab_Renamer.exe wurde nicht erstellt." + echo "" + echo "Prüfe die Fehlermeldungen oben und versuche:" + echo " 1. ./build_windows_wine.sh (Wine-Alternative)" + echo " 2. Build auf nativem Windows mit build_windows.bat" + echo "" + exit 1 +fi diff --git a/release/PointCab_Renamer_v4.2.1/build_windows_wine.sh b/release/PointCab_Renamer_v4.2.1/build_windows_wine.sh new file mode 100755 index 0000000..26479fa --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/build_windows_wine.sh @@ -0,0 +1,274 @@ +#!/bin/bash +# ============================================================================ +# PointCab Renamer - Windows Build unter Linux (Wine-Methode) +# Version: 4.2.1 +# ============================================================================ +# +# WICHTIG: Wine-basierte Builds sind EXPERIMENTELL und können fehlschlagen! +# +# BEKANNTE EINSCHRÄNKUNGEN: +# - Headless Server (ohne GUI): Python-Installation kann fehlschlagen +# - Manche Wine-Versionen haben Kompatibilitätsprobleme +# - GUI-basierte Python-Installer benötigen X11/Display +# +# EMPFOHLENE ALTERNATIVEN: +# 1. Windows-PC: build_windows.bat direkt ausführen +# 2. GitHub Actions: CI/CD für automatisierte Builds +# 3. Dual-Boot/VM: Windows-Build in echter Windows-Umgebung +# +# ============================================================================ + +# Farben für Ausgabe +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Konfiguration +PYTHON_VERSION="3.10.11" +PYTHON_INSTALLER="python-${PYTHON_VERSION}-amd64.exe" +PYTHON_URL="https://www.python.org/ftp/python/${PYTHON_VERSION}/${PYTHON_INSTALLER}" +WINE_PREFIX="$HOME/.wine_python" + +# Banner +echo -e "${BLUE}" +echo "============================================================================" +echo " PointCab Renamer - Windows Cross-Compilation (Wine-Methode)" +echo " Version 4.2.1 - EXPERIMENTELL" +echo "============================================================================" +echo -e "${NC}" + +echo -e "${YELLOW}╔════════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${YELLOW}║ WARNUNG: Diese Methode ist EXPERIMENTELL und kann fehlschlagen! ║${NC}" +echo -e "${YELLOW}╚════════════════════════════════════════════════════════════════════╝${NC}" +echo "" +echo "Bekannte Probleme:" +echo " • Headless Server ohne X11: Python-Installer kann hängen" +echo " • Wine-Kompatibilität variiert je nach Version" +echo " • ~500MB Download und 10+ Minuten Installationszeit" +echo "" +echo "Empfohlene Alternativen:" +echo " 1. Windows-PC: build_windows.bat ausführen" +echo " 2. GitHub Actions: Automatisierte CI/CD Builds" +echo "" +read -p "Trotzdem fortfahren? (j/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Jj]$ ]]; then + echo "" + echo "Abgebrochen. Alternative Methoden:" + echo " • Kopiere das Projekt auf einen Windows-PC" + echo " • Führe dort build_windows.bat aus" + exit 0 +fi +echo "" + +# Arbeitsverzeichnis +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Funktion für Fehlerbehandlung +fail_with_alternatives() { + echo "" + echo -e "${RED}╔════════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${RED}║ BUILD FEHLGESCHLAGEN ║${NC}" + echo -e "${RED}╚════════════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo -e "${YELLOW}Alternative Methoden:${NC}" + echo "" + echo " 1. ${GREEN}Windows-PC (EMPFOHLEN):${NC}" + echo " - Kopiere das gesamte Projektverzeichnis" + echo " - Führe build_windows.bat aus" + echo "" + echo " 2. ${GREEN}GitHub Actions:${NC}" + echo " - Push zu GitHub mit Actions Workflow" + echo " - Automatisierter Windows-Build" + echo "" + echo " 3. ${GREEN}Virtual Machine:${NC}" + echo " - Windows-VM mit VirtualBox/VMware" + echo " - Shared Folder für Projektdateien" + echo "" + exit 1 +} + +# Prüfe Wine +echo -e "${BLUE}[1/7] Prüfe Wine-Installation...${NC}" +if ! command -v wine &> /dev/null; then + echo -e "${RED}FEHLER: Wine ist nicht installiert!${NC}" + echo "" + echo "Installation auf Ubuntu/Debian:" + echo " sudo dpkg --add-architecture i386" + echo " sudo apt update" + echo " sudo apt install wine64 wine32" + echo "" + echo "Installation auf Fedora:" + echo " sudo dnf install wine" + echo "" + fail_with_alternatives +fi + +WINE_VERSION=$(wine --version 2>/dev/null || echo "unknown") +echo -e "${GREEN}✓ Wine installiert: $WINE_VERSION${NC}" + +# Prüfe auf Display (wichtig für GUI-Installer) +echo -e "${BLUE}[2/7] Prüfe Display-Umgebung...${NC}" +if [ -z "$DISPLAY" ]; then + echo -e "${YELLOW}⚠ Kein DISPLAY gesetzt - Headless-Modus erkannt${NC}" + echo "" + echo "Python-Installer benötigt möglicherweise X11/GUI." + echo "Dies kann in Headless-Umgebungen fehlschlagen." + echo "" + echo "Optionen:" + echo " • Virtuelles Display mit Xvfb (experimentell)" + echo " • X11 Forwarding bei SSH (-X Option)" + echo " • Desktop-Umgebung verwenden" + echo "" + + # Versuche Xvfb falls verfügbar + if command -v Xvfb &> /dev/null; then + echo "Xvfb gefunden - starte virtuelles Display..." + Xvfb :99 -screen 0 1024x768x24 & + XVFB_PID=$! + export DISPLAY=:99 + sleep 2 + echo -e "${GREEN}✓ Virtuelles Display gestartet (:99)${NC}" + else + echo -e "${YELLOW}Xvfb nicht installiert. Versuche ohne Display...${NC}" + echo " Installation: sudo apt install xvfb" + fi +else + echo -e "${GREEN}✓ Display vorhanden: $DISPLAY${NC}" +fi + +# Prüfe Projektdateien +echo -e "${BLUE}[3/7] Prüfe Projektdateien...${NC}" +if [ ! -f "pointcab_renamer.py" ]; then + echo -e "${RED}FEHLER: pointcab_renamer.py nicht gefunden!${NC}" + exit 1 +fi +if [ ! -f "cluster_cleanup.txt" ]; then + echo -e "${RED}FEHLER: cluster_cleanup.txt nicht gefunden!${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Projektdateien vorhanden${NC}" + +# Wine-Prefix initialisieren +echo -e "${BLUE}[4/7] Initialisiere Wine-Umgebung...${NC}" +export WINEPREFIX="$WINE_PREFIX" +export WINEARCH=win64 +export WINEDEBUG=-all # Unterdrücke Wine Debug-Meldungen + +if [ ! -d "$WINE_PREFIX" ]; then + echo "Erstelle Wine-Prefix unter: $WINE_PREFIX" + echo "Dies kann einige Minuten dauern..." + wineboot --init 2>/dev/null || true + sleep 5 +fi +echo -e "${GREEN}✓ Wine-Umgebung bereit${NC}" + +# Python-Pfade in Wine +WINE_PYTHON="$WINE_PREFIX/drive_c/Python310/python.exe" +WINE_PIP="$WINE_PREFIX/drive_c/Python310/Scripts/pip.exe" +WINE_PYINSTALLER="$WINE_PREFIX/drive_c/Python310/Scripts/pyinstaller.exe" + +# Python installieren falls nicht vorhanden +echo -e "${BLUE}[5/7] Prüfe Windows-Python...${NC}" +if [ ! -f "$WINE_PYTHON" ]; then + echo "Windows-Python nicht gefunden, installiere..." + echo "" + + # Download Python installer + if [ ! -f "/tmp/$PYTHON_INSTALLER" ]; then + echo "Lade Python herunter: $PYTHON_URL" + echo "Größe: ~28MB" + wget -q --show-progress -O "/tmp/$PYTHON_INSTALLER" "$PYTHON_URL" || { + echo -e "${RED}FEHLER: Python-Download fehlgeschlagen!${NC}" + fail_with_alternatives + } + fi + + # Installiere Python + echo "" + echo "Installiere Python in Wine..." + echo "Dies kann 5-10 Minuten dauern..." + echo "" + + # Silent Installation mit Timeout + timeout 300 wine "/tmp/$PYTHON_INSTALLER" /quiet InstallAllUsers=0 PrependPath=1 Include_test=0 2>/dev/null || { + echo -e "${YELLOW}⚠ Python-Installation möglicherweise fehlgeschlagen oder Timeout${NC}" + } + + sleep 5 + + # Prüfe Installation + if [ ! -f "$WINE_PYTHON" ]; then + echo "" + echo -e "${RED}FEHLER: Python-Installation fehlgeschlagen!${NC}" + echo "" + echo "Mögliche Ursachen:" + echo " • Headless-Umgebung ohne GUI" + echo " • Wine-Kompatibilitätsproblem" + echo " • Nicht genug Speicherplatz" + echo "" + + # Cleanup Xvfb falls gestartet + [ -n "$XVFB_PID" ] && kill $XVFB_PID 2>/dev/null + + fail_with_alternatives + fi +fi +echo -e "${GREEN}✓ Windows-Python installiert${NC}" + +# PyInstaller installieren +echo -e "${BLUE}[6/7] Prüfe PyInstaller...${NC}" +if [ ! -f "$WINE_PYINSTALLER" ]; then + echo "Installiere PyInstaller..." + wine "$WINE_PIP" install pyinstaller 2>/dev/null || { + echo -e "${RED}FEHLER: PyInstaller-Installation fehlgeschlagen!${NC}" + fail_with_alternatives + } +fi +echo -e "${GREEN}✓ PyInstaller installiert${NC}" + +# Aufräumen +echo -e "${BLUE}[7/7] Erstelle Windows .exe...${NC}" +rm -rf build/ dist/ *.spec 2>/dev/null || true + +echo "Build läuft... (kann einige Minuten dauern)" +echo "" + +# Führe PyInstaller aus +wine "$WINE_PYINSTALLER" \ + --onefile \ + --windowed \ + --name "PointCab_Renamer" \ + --add-data "cluster_cleanup.txt;." \ + pointcab_renamer.py 2>&1 | grep -v "^fixme:" | grep -v "^err:" || true + +# Cleanup Xvfb falls gestartet +[ -n "$XVFB_PID" ] && kill $XVFB_PID 2>/dev/null + +# Prüfe Ergebnis +echo "" +if [ -f "dist/PointCab_Renamer.exe" ]; then + echo -e "${GREEN}╔════════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ BUILD ERFOLGREICH! ║${NC}" + echo -e "${GREEN}╚════════════════════════════════════════════════════════════════════╝${NC}" + + # Kopiere zusätzliche Dateien + cp cluster_cleanup.txt dist/ + cp BENUTZERHANDBUCH.md dist/ 2>/dev/null || true + + echo "" + echo "Erstellte Dateien:" + ls -lh dist/ + echo "" + echo "Die .exe befindet sich in: $SCRIPT_DIR/dist/" + echo "" + echo -e "${YELLOW}WICHTIG:${NC}" + echo " Die .exe sollte auf einem echten Windows-System" + echo " getestet werden, bevor sie verteilt wird!" + echo "" +else + fail_with_alternatives +fi diff --git a/release/PointCab_Renamer_v4.2.1/cluster_cleanup.txt b/release/PointCab_Renamer_v4.2.1/cluster_cleanup.txt new file mode 100644 index 0000000..4a7f921 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/cluster_cleanup.txt @@ -0,0 +1,16 @@ +# Konfigurationsdatei für Clustername-Bereinigung +# Jede Zeile enthält einen String, der aus dem Stammverzeichnisnamen entfernt wird +# Leerzeilen und Zeilen die mit # beginnen werden ignoriert +# +# Beispiel: Stammverzeichnis "Am_Upstall_4_re" wird zu Clustername "Am_Upstall_4" +# +_re +_li +_mi +_mi-li +_mi-re +_part_1 +_part_2 +_part_3 +_part_4 +_part_5 diff --git a/release/PointCab_Renamer_v4.2.1/dist_linux/BENUTZERHANDBUCH.md b/release/PointCab_Renamer_v4.2.1/dist_linux/BENUTZERHANDBUCH.md new file mode 100644 index 0000000..053639e --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/dist_linux/BENUTZERHANDBUCH.md @@ -0,0 +1,385 @@ +# PointCab Renamer - Benutzerhandbuch + +**Version 4.1** | Datum: 14. Januar 2026 + +--- + +## Inhaltsverzeichnis + +1. [Einführung](#einführung) +2. [Installation](#installation) +3. [Programmstart](#programmstart) +4. [Die drei Modi](#die-drei-modi) + - [Einzelprojekt-Modus](#einzelprojekt-modus) + - [Batch-Modus](#batch-modus) + - [Projekt-Merger](#projekt-merger) +5. [Konfiguration](#konfiguration) +6. [Troubleshooting](#troubleshooting) +7. [FAQ](#faq) + +--- + +## Einführung + +### Was ist der PointCab Renamer? + +Der **PointCab Renamer** ist ein Werkzeug zur automatischen Umbenennung von PointCab-Projektdateien. Es löst das Problem, dass PointCab-Scandateien oft kryptische Namen haben (z.B. `1.lsd`, `2.lsd`) und benennt diese nach einem einheitlichen Schema um: + +**Format:** `[ClusterName]_[ScanName].[Erweiterung]` + +**Beispiel:** `EG_Flur_scan001.lsd` + +### Hauptfunktionen + +- **Einzelprojekt-Modus**: Ein einzelnes PointCab-Projekt umbenennen +- **Batch-Modus**: Mehrere Projekte gleichzeitig verarbeiten +- **Projekt-Merger**: Mehrere Projekte in ein Zielprojekt zusammenführen +- **Cluster-Bereinigung**: Automatische Entfernung von Suffixen wie `_re`, `_li` aus Clusternamen +- **Detailliertes Logging**: Vollständige Protokollierung aller Änderungen + +--- + +## Installation + +### Windows + +1. Laden Sie die Datei `pointcab_renamer.exe` herunter +2. Speichern Sie die Datei in einem beliebigen Ordner (z.B. `C:\Tools\`) +3. Kopieren Sie die `cluster_cleanup.txt` in denselben Ordner +4. Starten Sie das Programm mit Doppelklick auf die `.exe` + +### Ubuntu/Linux + +1. Laden Sie die Datei `pointcab_renamer` herunter +2. Speichern Sie die Datei in einem beliebigen Ordner (z.B. `/home/benutzer/tools/`) +3. Kopieren Sie die `cluster_cleanup.txt` in denselben Ordner +4. Machen Sie die Datei ausführbar: + ```bash + chmod +x pointcab_renamer + ``` +5. Starten Sie das Programm: + ```bash + ./pointcab_renamer + ``` + +### Aus dem Quellcode (für Entwickler) + +1. Stellen Sie sicher, dass Python 3.8+ installiert ist +2. Laden Sie den Quellcode herunter +3. Starten Sie mit: + ```bash + python pointcab_renamer.py + ``` + +--- + +## Programmstart + +### Hauptmenü + +Nach dem Start erscheint das Hauptmenü mit drei Optionen: + +``` +╔═══════════════════════════════════════════╗ +║ PointCab Renamer v4.1 ║ +╠═══════════════════════════════════════════╣ +║ ║ +║ [Einzelprojekt umbenennen] ║ +║ ║ +║ [Batch-Verarbeitung] ║ +║ ║ +║ [Projekt Merger] ║ +║ ║ +╚═══════════════════════════════════════════╝ +``` + +--- + +## Die drei Modi + +### Einzelprojekt-Modus + +**Verwendung:** Wenn Sie ein einzelnes PointCab-Projekt umbenennen möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Einzelprojekt umbenennen"** +2. Wählen Sie die **LSDX-Projektdatei** aus (z.B. `Am_Upstall_4.lsdx`) +3. Wählen Sie den **PointCloud-Ordner** aus (enthält die `.lsd` Dateien) +4. Das Programm zeigt eine **Vorschau** der Änderungen: + ``` + Vorschau der Umbenennung: + ───────────────────────── + 1.lsd → EG_Flur_scan001.lsd + 2.lsd → EG_Flur_scan002.lsd + 3.lsd → OG_Bad_scan001.lsd + ... + ``` +5. Klicken Sie auf **"Umbenennen starten"** +6. Nach Abschluss wird ein Protokoll angezeigt + +#### Dateistruktur (Vorher → Nachher) + +**Vorher:** +``` +Am_Upstall_4_PointCloud/ +├── 1.lsd +├── 2.lsd +├── 3.lsd +└── ... +``` + +**Nachher:** +``` +Am_Upstall_4_PointCloud/ +├── EG_Flur_scan001.lsd +├── EG_Flur_scan002.lsd +├── OG_Bad_scan001.lsd +└── ... +``` + +--- + +### Batch-Modus + +**Verwendung:** Wenn Sie mehrere PointCab-Projekte auf einmal verarbeiten möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Batch-Verarbeitung"** +2. Wählen Sie den **Basisordner** aus, der alle Projekte enthält +3. Das Programm erkennt automatisch alle PointCab-Projekte: + ``` + Gefundene Projekte: + ───────────────────── + ☑ Projekt_A (15 Scans) + ☑ Projekt_B (22 Scans) + ☑ Projekt_C (8 Scans) + ``` +4. Wählen Sie die gewünschten Projekte aus (oder behalten Sie alle ausgewählt) +5. Klicken Sie auf **"Batch-Verarbeitung starten"** +6. Der Fortschritt wird angezeigt: + ``` + Verarbeite Projekt 1/3: Projekt_A + [████████████░░░░░░░░] 60% + ``` +7. Nach Abschluss wird eine Zusammenfassung angezeigt + +#### Erwartete Ordnerstruktur + +``` +Basisordner/ +├── Projekt_A/ +│ ├── Projekt_A.lsdx +│ └── Projekt_A_PointCloud/ +│ ├── 1.lsd +│ └── ... +├── Projekt_B/ +│ ├── Projekt_B.lsdx +│ └── Projekt_B_PointCloud/ +└── Projekt_C/ + ├── Projekt_C.lsdx + └── Projekt_C_PointCloud/ +``` + +--- + +### Projekt-Merger + +**Verwendung:** Wenn Sie mehrere PointCab-Projekte in ein einziges Projekt zusammenführen möchten. + +#### Schritt-für-Schritt-Anleitung + +1. Klicken Sie auf **"Projekt Merger"** +2. Wählen Sie den **Modus**: + - **Einzelprojekt zusammenführen**: Ein Quellprojekt → Zielprojekt + - **Batch-Merge**: Mehrere Quellprojekte → Zielprojekt +3. Wählen Sie das **Zielprojekt** (in das zusammengeführt wird) +4. Wählen Sie das/die **Quellprojekt(e)** +5. Das Programm zeigt eine **Vorschau** mit Konfliktauflösung: + ``` + Merge-Vorschau: + ───────────────────── + Zielprojekt: Haupt_Projekt (5 Cluster, 25 Scans) + Quellprojekt: Teil_A (2 Cluster, 10 Scans) + + Zu übertragende Dateien: + - EG_Flur_scan001.lsd + - EG_Flur_scan002.lsd (Konflikt → EG_Flur_scan002_merged_1.lsd) + - ... + ``` +6. Klicken Sie auf **"Merge starten"** +7. Nach Abschluss werden die Statistiken angezeigt: + ``` + Merge abgeschlossen! + ───────────────────── + Cluster vorher: 5 → nachher: 7 + Scans vorher: 25 → nachher: 35 + Dateien kopiert: 10 + Konflikte gelöst: 1 + ``` + +#### Konfliktauflösung + +Wenn eine Datei im Zielprojekt bereits existiert: +- Die neue Datei wird umbenannt: `dateiname_merged_1.lsd` +- Bei weiteren Konflikten: `dateiname_merged_2.lsd`, etc. +- Die LSDX-Datei wird entsprechend aktualisiert + +#### Wichtige Hinweise + +- **Backup**: Das Zielprojekt wird vor dem Merge gesichert (`.lsdx.backup`) +- **UUID-Regenerierung**: Alle übertragenen Elemente erhalten neue eindeutige IDs +- **Cluster-Duplikate**: Gleichnamige Cluster werden zusammengeführt + +--- + +## Konfiguration + +### Die Datei cluster_cleanup.txt + +Diese Konfigurationsdatei definiert, welche Suffixe aus Clusternamen entfernt werden sollen. + +#### Speicherort + +- **Windows**: Im selben Ordner wie `pointcab_renamer.exe` +- **Linux**: Im selben Ordner wie `pointcab_renamer` + +#### Format + +``` +# Dies ist ein Kommentar +_re +_li +_mi +_mi-li +_mi-re +``` + +- Jede Zeile = ein zu entfernender String +- Zeilen mit `#` am Anfang sind Kommentare +- Leere Zeilen werden ignoriert + +#### Beispiel + +Mit der obigen Konfiguration: +- `Flur_re` → `Flur` +- `Bad_mi-li` → `Bad` +- `Küche_li` → `Küche` + +#### Konfiguration neu laden + +Änderungen an `cluster_cleanup.txt` werden nach einem Neustart oder über den Button **"Konfiguration neu laden"** übernommen. + +--- + +## Troubleshooting + +### Häufige Fehler und Lösungen + +#### "LSDX-Datei nicht gefunden" + +**Problem**: Das Programm kann die Projektdatei nicht finden. + +**Lösung**: +- Stellen Sie sicher, dass die `.lsdx`-Datei im Projektordner existiert +- Prüfen Sie, ob Sie Leserechte für die Datei haben +- Der Dateiname sollte mit `.lsdx` enden (nicht `.LSDX`) + +#### "PointCloud-Ordner nicht gefunden" + +**Problem**: Der Ordner mit den Scandaten fehlt. + +**Lösung**: +- Der Ordner muss `_PointCloud` im Namen haben +- Beispiel: `Projekt_A_PointCloud` +- Prüfen Sie die Ordnerstruktur + +#### "Keine Projekte gefunden" (Batch-Modus) + +**Problem**: Im Basisordner werden keine Projekte erkannt. + +**Lösung**: +- Jedes Projekt muss eine `.lsdx`-Datei und einen `_PointCloud`-Ordner haben +- Der Projektname in der LSDX muss mit dem Ordnernamen übereinstimmen + +#### "Zugriff verweigert" + +**Problem**: Dateien können nicht umbenannt werden. + +**Lösung**: +- Schließen Sie PointCab +- Prüfen Sie, ob andere Programme die Dateien verwenden +- Unter Windows: Als Administrator ausführen +- Unter Linux: Prüfen Sie die Dateiberechtigungen + +#### "GUI startet nicht" (Linux) + +**Problem**: Keine grafische Oberfläche unter Linux. + +**Lösung**: +- Installieren Sie tkinter: `sudo apt install python3-tk` +- Stellen Sie sicher, dass ein Display verfügbar ist + +--- + +## FAQ + +### Allgemeine Fragen + +**F: Werden die Originaldateien gelöscht?** + +A: Nein, die Dateien werden nur umbenannt. Es werden keine Daten gelöscht. + +**F: Kann ich die Umbenennung rückgängig machen?** + +A: Die ursprünglichen Namen werden im Log-Datei protokolliert. Eine automatische Rückgängig-Funktion gibt es nicht. + +**F: Funktioniert das Tool auch mit älteren PointCab-Versionen?** + +A: Das Tool wurde für PointCab-Projekte mit LSDX-Format entwickelt. Ältere Formate werden möglicherweise nicht unterstützt. + +**F: Wie viele Projekte kann ich im Batch-Modus verarbeiten?** + +A: Es gibt keine feste Grenze. Die Verarbeitungszeit hängt von der Anzahl der Scans ab. + +### Technische Fragen + +**F: Wo werden die Log-Dateien gespeichert?** + +A: Im Projektordner, mit dem Format: +- Einzelprojekt: `rename_YYYYMMDD_HHMMSS.log` +- Batch: `batch_YYYYMMDD_HHMMSS.log` +- Merger: `merge_YYYYMMDD_HHMMSS.log` + +**F: Was passiert bei einem Stromausfall während der Verarbeitung?** + +A: Die bereits umbenannten Dateien bleiben umbenannt. Die LSDX-Datei wird erst nach erfolgreicher Umbenennung aktualisiert. + +**F: Kann ich das Tool über die Kommandozeile nutzen?** + +A: Aktuell nur mit grafischer Oberfläche. Kommandozeilen-Unterstützung ist für eine zukünftige Version geplant. + +### Merger-Fragen + +**F: Was passiert mit den Originalprojekten beim Merge?** + +A: Die Quellprojekte werden nicht verändert. Dateien werden kopiert, nicht verschoben. + +**F: Kann ich den Merge rückgängig machen?** + +A: Die ursprüngliche LSDX wird als `.backup` gespeichert. Die kopierten Dateien müssen manuell gelöscht werden. + +**F: Werden alle Unterordner (Previews, etc.) auch gemergt?** + +A: Ja, alle relevanten Unterordner werden übertragen. + +--- + +## Support + +Bei Fragen oder Problemen wenden Sie sich an Ihren Administrator. + +--- + +*PointCab Renamer v4.1 - © 2026* diff --git a/release/PointCab_Renamer_v4.2.1/dist_linux/cluster_cleanup.txt b/release/PointCab_Renamer_v4.2.1/dist_linux/cluster_cleanup.txt new file mode 100644 index 0000000..4a7f921 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/dist_linux/cluster_cleanup.txt @@ -0,0 +1,16 @@ +# Konfigurationsdatei für Clustername-Bereinigung +# Jede Zeile enthält einen String, der aus dem Stammverzeichnisnamen entfernt wird +# Leerzeilen und Zeilen die mit # beginnen werden ignoriert +# +# Beispiel: Stammverzeichnis "Am_Upstall_4_re" wird zu Clustername "Am_Upstall_4" +# +_re +_li +_mi +_mi-li +_mi-re +_part_1 +_part_2 +_part_3 +_part_4 +_part_5 diff --git a/release/PointCab_Renamer_v4.2.1/dist_linux/pointcab_renamer b/release/PointCab_Renamer_v4.2.1/dist_linux/pointcab_renamer new file mode 100755 index 0000000..554c23d Binary files /dev/null and b/release/PointCab_Renamer_v4.2.1/dist_linux/pointcab_renamer differ diff --git a/release/PointCab_Renamer_v4.2.1/pointcab_renamer.py b/release/PointCab_Renamer_v4.2.1/pointcab_renamer.py new file mode 100644 index 0000000..60d3379 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/pointcab_renamer.py @@ -0,0 +1,2058 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +PointCab Project Renamer v4.2 +A GUI tool for renaming scans in PointCab projects with full scan names. +Now with Batch Renamer and Project Merger support! + +LSDX-Struktur: +- + - + - + - # Wurzelelement + - + - + - + - 1.lsd + - Previews/1.png + +Author: DeepAgent +Date: 2026-01-14 +""" + +import os +import re +import shutil +import logging +import tkinter as tk +from tkinter import ttk, filedialog, messagebox, scrolledtext +from datetime import datetime +import xml.etree.ElementTree as ET +from pathlib import Path +import uuid +import copy + + +class PointCabRenamer: + """Main application class for PointCab project renaming (Single Project Mode).""" + + def __init__(self, root, return_callback=None): + self.root = root + self.return_callback = return_callback + self.root.title("PointCab Projekt Umbenenner - Einzelprojekt") + self.root.geometry("850x750") + self.root.minsize(700, 600) + + # Variables + self.root_dir = tk.StringVar() + self.pointcloud_dir = None + self.lsdx_file = None + self.preview_dir = None + self.changes = [] + self.logger = None + self.cleanup_strings = [] + + # Load cleanup configuration + self.load_cleanup_config() + + self.setup_ui() + + def load_cleanup_config(self): + """Load cleanup strings from cluster_cleanup.txt.""" + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cluster_cleanup.txt") + self.cleanup_strings = [] + + if os.path.exists(config_path): + try: + # Use utf-8-sig to handle BOM from Windows editors + with open(config_path, 'r', encoding='utf-8-sig') as f: + for line in f: + # Strip whitespace including BOM characters + line = line.strip() + # Skip empty lines and comments + if not line: + continue + if line.startswith('#'): + continue + # Remove any remaining BOM or special chars + line = line.lstrip('\ufeff') + if line: + self.cleanup_strings.append(line) + print(f"Cleanup-Konfiguration geladen: {len(self.cleanup_strings)} Einträge") + except Exception as e: + print(f"Fehler beim Laden der Cleanup-Konfiguration: {e}") + + def clean_cluster_name(self, name): + """Remove cleanup strings from the cluster name.""" + cleaned_name = name + removed_strings = [] + + for cleanup_str in self.cleanup_strings: + if cleanup_str in cleaned_name: + removed_strings.append(cleanup_str) + cleaned_name = cleaned_name.replace(cleanup_str, '') + + return cleaned_name, removed_strings + + def setup_ui(self): + """Setup the user interface.""" + # Main frame with padding + main_frame = ttk.Frame(self.root, padding="10") + main_frame.pack(fill=tk.BOTH, expand=True) + + # Back button if we have a return callback + if self.return_callback: + back_frame = ttk.Frame(main_frame) + back_frame.pack(fill=tk.X, pady=(0, 10)) + ttk.Button(back_frame, text="← Zurück zum Hauptmenü", command=self.go_back).pack(side=tk.LEFT) + + # Directory selection frame + dir_frame = ttk.LabelFrame(main_frame, text="Projektverzeichnis", padding="5") + dir_frame.pack(fill=tk.X, pady=(0, 10)) + + ttk.Entry(dir_frame, textvariable=self.root_dir, width=60).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) + ttk.Button(dir_frame, text="Durchsuchen...", command=self.select_directory).pack(side=tk.LEFT) + ttk.Button(dir_frame, text="Analysieren", command=self.analyze_project).pack(side=tk.LEFT, padx=(5, 0)) + + # Info frame + self.info_frame = ttk.LabelFrame(main_frame, text="Projektinformationen", padding="5") + self.info_frame.pack(fill=tk.X, pady=(0, 10)) + + self.info_label = ttk.Label(self.info_frame, text="Bitte wählen Sie ein Projektverzeichnis aus.", wraplength=800) + self.info_label.pack(fill=tk.X) + + # Cleanup config info frame + cleanup_frame = ttk.LabelFrame(main_frame, text="Clustername-Bereinigung", padding="5") + cleanup_frame.pack(fill=tk.X, pady=(0, 10)) + + cleanup_info = f"Strings die aus dem Clusternamen entfernt werden: {', '.join(self.cleanup_strings) if self.cleanup_strings else '(keine)'}\n" + cleanup_info += f"📁 Konfigurationsdatei: cluster_cleanup.txt (im Programmverzeichnis)" + self.cleanup_label = ttk.Label(cleanup_frame, text=cleanup_info, wraplength=800, foreground="gray") + self.cleanup_label.pack(fill=tk.X) + + ttk.Button(cleanup_frame, text="Konfiguration neu laden", command=self.reload_cleanup_config).pack(side=tk.LEFT, pady=(5, 0)) + + # Changes preview frame + preview_frame = ttk.LabelFrame(main_frame, text="Vorschau der Änderungen", padding="5") + preview_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + self.preview_text = scrolledtext.ScrolledText(preview_frame, wrap=tk.WORD, height=20, font=('Consolas', 9)) + self.preview_text.pack(fill=tk.BOTH, expand=True) + self.preview_text.config(state=tk.DISABLED) + + # Status frame + status_frame = ttk.Frame(main_frame) + status_frame.pack(fill=tk.X, pady=(0, 10)) + + self.status_label = ttk.Label(status_frame, text="Bereit", foreground="gray") + self.status_label.pack(side=tk.LEFT) + + self.progress = ttk.Progressbar(status_frame, mode='indeterminate', length=200) + self.progress.pack(side=tk.RIGHT) + + # Buttons frame + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=tk.X) + + self.execute_btn = ttk.Button(button_frame, text="Änderungen ausführen", command=self.execute_changes, state=tk.DISABLED) + self.execute_btn.pack(side=tk.RIGHT) + + ttk.Button(button_frame, text="Beenden", command=self.root.quit).pack(side=tk.LEFT) + + def go_back(self): + """Return to main menu.""" + if self.return_callback: + self.return_callback() + + def reload_cleanup_config(self): + """Reload cleanup configuration and update display.""" + self.load_cleanup_config() + cleanup_info = f"Strings die aus dem Clusternamen entfernt werden: {', '.join(self.cleanup_strings) if self.cleanup_strings else '(keine)'}\n" + cleanup_info += f"📁 Konfigurationsdatei: cluster_cleanup.txt (im Programmverzeichnis)" + self.cleanup_label.config(text=cleanup_info) + messagebox.showinfo("Info", f"Konfiguration neu geladen.\n{len(self.cleanup_strings)} Cleanup-Strings gefunden.") + + def select_directory(self): + """Open directory selection dialog.""" + directory = filedialog.askdirectory(title="Stammverzeichnis auswählen") + if directory: + self.root_dir.set(directory) + self.analyze_project() + + def setup_logging(self, log_dir): + """Setup logging to file.""" + log_file = os.path.join(log_dir, "renamer.log") + + # Create logger + self.logger = logging.getLogger('PointCabRenamer') + self.logger.setLevel(logging.INFO) + + # Clear existing handlers + self.logger.handlers.clear() + + # File handler + fh = logging.FileHandler(log_file, encoding='utf-8') + fh.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + fh.setFormatter(formatter) + self.logger.addHandler(fh) + + return log_file + + def find_pointcloud_folder(self, root_dir): + """Find the PointCloud folder in the root directory.""" + root_name = os.path.basename(root_dir) + expected_name = f"{root_name}_PointCloud" + + # Check for expected folder name + expected_path = os.path.join(root_dir, expected_name) + if os.path.isdir(expected_path): + return expected_path + + # Search for any folder ending with _PointCloud + for item in os.listdir(root_dir): + item_path = os.path.join(root_dir, item) + if os.path.isdir(item_path) and item.endswith("_PointCloud"): + return item_path + + return None + + def find_lsdx_file(self, pointcloud_dir): + """Find the LSDX file in the PointCloud directory.""" + for item in os.listdir(pointcloud_dir): + if item.endswith(".lsdx"): + return os.path.join(pointcloud_dir, item) + return None + + def get_file_mapping(self, file_list, extension, scan_prefix): + """Create mapping for files with full scan names.""" + # Extract numbers from filenames + files_with_nums = [] + for f in file_list: + match = re.match(r'^(\d+)\.' + extension + '$', f) + if match: + num = int(match.group(1)) + files_with_nums.append((num, f)) + + if not files_with_nums: + return {}, 2 + + files_with_nums.sort(key=lambda x: x[0]) + max_num = max(num for num, _ in files_with_nums) + + # Determine padding width + if max_num >= 100: + width = 3 + else: + width = 2 + + # Create mapping with full scan name + mapping = {} + for num, old_name in files_with_nums: + new_num_str = str(num).zfill(width) + new_name = f"{scan_prefix}_{new_num_str}.{extension}" + if old_name != new_name: + mapping[old_name] = new_name + + return mapping, width + + def get_scan_name_mappings(self, lsdx_file, width, scan_prefix): + """Get scan name mappings from LSDX file.""" + mappings = [] + try: + tree = ET.parse(lsdx_file) + root = tree.getroot() + + # Find all scan elements with name attribute + for element in root.findall(".//Element[@type='scan']"): + old_name = element.get('name') + if old_name: + # Try to extract number from name + match = re.match(r'^(\d+)$', old_name) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(width) + new_name = f"{scan_prefix}_{new_num_str}" + if old_name != new_name: + mappings.append((old_name, new_name)) + except Exception as e: + print(f"Fehler beim Lesen der Scan-Namen: {e}") + + return mappings + + def analyze_project(self): + """Analyze the selected project directory.""" + root_dir = self.root_dir.get() + + if not root_dir or not os.path.isdir(root_dir): + messagebox.showerror("Fehler", "Bitte wählen Sie ein gültiges Verzeichnis aus.") + return + + self.update_status("Analysiere Projekt...") + self.progress.start() + self.changes = [] + + try: + # Setup logging + log_file = self.setup_logging(root_dir) + self.logger.info(f"Analyse gestartet für: {root_dir}") + + # Find PointCloud folder + self.pointcloud_dir = self.find_pointcloud_folder(root_dir) + if not self.pointcloud_dir: + messagebox.showerror("Fehler", "PointCloud-Ordner nicht gefunden!") + self.progress.stop() + self.update_status("Fehler: PointCloud-Ordner nicht gefunden") + return + + # Find LSDX file + self.lsdx_file = self.find_lsdx_file(self.pointcloud_dir) + if not self.lsdx_file: + messagebox.showerror("Fehler", "LSDX-Datei nicht gefunden!") + self.progress.stop() + self.update_status("Fehler: LSDX-Datei nicht gefunden") + return + + # Find Previews folder + self.preview_dir = os.path.join(self.pointcloud_dir, "Previews") + if not os.path.isdir(self.preview_dir): + self.preview_dir = None + + # Get root name and clean cluster name + root_name = os.path.basename(root_dir) + cluster_name, removed_strings = self.clean_cluster_name(root_name) + scan_prefix = root_name + + # Update info + info_text = f"Stammverzeichnis: {root_name}\n" + info_text += f"PointCloud-Ordner: {os.path.basename(self.pointcloud_dir)}\n" + info_text += f"LSDX-Datei: {os.path.basename(self.lsdx_file)}\n" + info_text += f"Preview-Ordner: {'Gefunden' if self.preview_dir else 'Nicht gefunden'}\n" + info_text += f"Clustername: {root_name} → {cluster_name}" + if removed_strings: + info_text += f" (entfernt: {', '.join(removed_strings)})" + info_text += f"\nScan-Namen-Präfix: {scan_prefix} (unverändert)" + self.info_label.config(text=info_text) + + # Collect LSD files + lsd_files = [f for f in os.listdir(self.pointcloud_dir) if f.endswith('.lsd')] + + # Get mapping for LSD files + lsd_mapping, lsd_width = self.get_file_mapping(lsd_files, 'lsd', scan_prefix) + + # Collect PNG files if preview folder exists + png_mapping = {} + png_width = lsd_width + if self.preview_dir: + png_files = [f for f in os.listdir(self.preview_dir) if f.endswith('.png')] + png_mapping, png_width = self.get_file_mapping(png_files, 'png', scan_prefix) + + # Get scan name mappings + scan_name_mappings = self.get_scan_name_mappings(self.lsdx_file, lsd_width, scan_prefix) + + # Build changes list + preview_text = "=== GEPLANTE ÄNDERUNGEN ===\n\n" + + # LSDX Backup + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_name = f"{os.path.basename(self.lsdx_file)}.backup_{timestamp}" + preview_text += f"📁 BACKUP der LSDX-Datei:\n" + preview_text += f" → {backup_name}\n\n" + self.changes.append(('backup', self.lsdx_file, os.path.join(self.pointcloud_dir, backup_name))) + + # LSD file renames + if lsd_mapping: + preview_text += f"📄 LSD-Dateien umbenennen ({len(lsd_mapping)} Dateien):\n" + for old, new in sorted(lsd_mapping.items(), key=lambda x: x[0]): + preview_text += f" {old} → {new}\n" + self.changes.append(('rename_lsd', + os.path.join(self.pointcloud_dir, old), + os.path.join(self.pointcloud_dir, new))) + preview_text += "\n" + else: + preview_text += "📄 LSD-Dateien: Keine Umbenennung erforderlich\n\n" + + # PNG file renames + if self.preview_dir and png_mapping: + preview_text += f"🖼️ PNG-Dateien umbenennen ({len(png_mapping)} Dateien):\n" + for old, new in sorted(png_mapping.items(), key=lambda x: x[0]): + preview_text += f" {old} → {new}\n" + self.changes.append(('rename_png', + os.path.join(self.preview_dir, old), + os.path.join(self.preview_dir, new))) + preview_text += "\n" + elif self.preview_dir: + preview_text += "🖼️ PNG-Dateien: Keine Umbenennung erforderlich\n\n" + else: + preview_text += "🖼️ PNG-Dateien: Preview-Ordner nicht vorhanden\n\n" + + # Scan name changes + if scan_name_mappings: + preview_text += f"🏷️ Scan-Namen in LSDX aktualisieren ({len(scan_name_mappings)} Scans):\n" + for old_name, new_name in scan_name_mappings[:10]: + preview_text += f" name=\"{old_name}\" → name=\"{new_name}\"\n" + if len(scan_name_mappings) > 10: + preview_text += f" ... und {len(scan_name_mappings) - 10} weitere\n" + preview_text += "\n" + else: + preview_text += "🏷️ Scan-Namen: Keine Änderungen erforderlich\n\n" + + # LSDX updates + preview_text += "📝 LSDX-Datei aktualisieren:\n" + preview_text += f" - Clustername: '{root_name}' → '{cluster_name}'" + if removed_strings: + preview_text += f" (entfernt: {', '.join(removed_strings)})" + preview_text += "\n" + preview_text += f" - Dateinamen in FilePath-Elementen mit vollständigem Scan-Namen\n" + preview_text += f" - Beispiel: 1.lsd → {scan_prefix}_01.lsd\n" + preview_text += f" - Scan-Namen mit Präfix '{scan_prefix}_' versehen\n" + + self.changes.append(('update_lsdx', self.lsdx_file, { + 'cluster_name': cluster_name, + 'scan_prefix': scan_prefix, + 'original_name': root_name, + 'removed_strings': removed_strings, + 'lsd_width': lsd_width, + 'png_width': png_width if self.preview_dir else lsd_width + })) + + preview_text += f"\n=== ZUSAMMENFASSUNG ===\n" + preview_text += f"Gesamtänderungen: {len(self.changes)}\n" + if removed_strings: + preview_text += f"Bereinigte Strings: {', '.join(removed_strings)}\n" + preview_text += f"Log-Datei: {log_file}\n" + + # Update preview + self.preview_text.config(state=tk.NORMAL) + self.preview_text.delete(1.0, tk.END) + self.preview_text.insert(tk.END, preview_text) + self.preview_text.config(state=tk.DISABLED) + + # Enable execute button if there are changes + if self.changes: + self.execute_btn.config(state=tk.NORMAL) + + self.logger.info(f"Analyse abgeschlossen: {len(self.changes)} Änderungen geplant") + self.logger.info(f"Clustername: {root_name} → {cluster_name}") + self.logger.info(f"Scan-Namen-Präfix: {scan_prefix}") + if removed_strings: + self.logger.info(f"Entfernte Strings: {', '.join(removed_strings)}") + self.update_status(f"Analyse abgeschlossen: {len(self.changes)} Änderungen geplant") + + except Exception as e: + messagebox.showerror("Fehler", f"Fehler bei der Analyse:\n{str(e)}") + if self.logger: + self.logger.error(f"Analysefehler: {str(e)}") + self.update_status("Fehler bei der Analyse") + + finally: + self.progress.stop() + + def execute_changes(self): + """Execute all planned changes.""" + if not self.changes: + messagebox.showinfo("Info", "Keine Änderungen zum Ausführen.") + return + + # Confirmation dialog + result = messagebox.askyesno( + "Bestätigung", + f"Möchten Sie wirklich {len(self.changes)} Änderungen ausführen?\n\n" + "Ein Backup der LSDX-Datei wird automatisch erstellt." + ) + + if not result: + return + + self.update_status("Führe Änderungen aus...") + self.progress.start() + self.execute_btn.config(state=tk.DISABLED) + + try: + self.logger.info("Starte Ausführung der Änderungen") + + # First pass: backup and collect rename operations + renames_lsd = [] + renames_png = [] + lsdx_update = None + + for change in self.changes: + if change[0] == 'backup': + # Create backup + _, src, dst = change + shutil.copy2(src, dst) + self.logger.info(f"Backup erstellt: {dst}") + elif change[0] == 'rename_lsd': + renames_lsd.append((change[1], change[2])) + elif change[0] == 'rename_png': + renames_png.append((change[1], change[2])) + elif change[0] == 'update_lsdx': + lsdx_update = change + + # Execute renames with temporary names to avoid conflicts + def safe_rename(rename_list, file_type): + """Rename files using temporary names to avoid conflicts.""" + temp_suffix = f"_temp_{datetime.now().strftime('%Y%m%d%H%M%S')}" + + # First: rename to temporary names + temp_mappings = [] + for src, dst in rename_list: + if os.path.exists(src): + temp_name = src + temp_suffix + os.rename(src, temp_name) + temp_mappings.append((temp_name, dst)) + self.logger.info(f"{file_type}: {os.path.basename(src)} → temp") + + # Second: rename from temporary to final names + for temp_name, dst in temp_mappings: + os.rename(temp_name, dst) + self.logger.info(f"{file_type}: temp → {os.path.basename(dst)}") + + safe_rename(renames_lsd, "LSD") + safe_rename(renames_png, "PNG") + + # Update LSDX file + if lsdx_update: + self.update_lsdx_file(lsdx_update[1], lsdx_update[2]) + + self.logger.info("Alle Änderungen erfolgreich ausgeführt") + self.update_status("Alle Änderungen erfolgreich ausgeführt!") + messagebox.showinfo("Erfolg", "Alle Änderungen wurden erfolgreich ausgeführt!") + + # Clear changes + self.changes = [] + self.preview_text.config(state=tk.NORMAL) + self.preview_text.delete(1.0, tk.END) + self.preview_text.insert(tk.END, "Alle Änderungen wurden erfolgreich ausgeführt.\n\nSie können nun ein neues Projekt auswählen.") + self.preview_text.config(state=tk.DISABLED) + + except Exception as e: + messagebox.showerror("Fehler", f"Fehler bei der Ausführung:\n{str(e)}") + self.logger.error(f"Ausführungsfehler: {str(e)}") + self.update_status("Fehler bei der Ausführung") + self.execute_btn.config(state=tk.NORMAL) + + finally: + self.progress.stop() + + def update_lsdx_file(self, lsdx_path, params): + """Update the LSDX file with new filenames, cluster name, and scan names.""" + cluster_name = params['cluster_name'] + scan_prefix = params.get('scan_prefix', params.get('original_name', cluster_name)) + original_name = params.get('original_name', cluster_name) + removed_strings = params.get('removed_strings', []) + lsd_width = params['lsd_width'] + png_width = params['png_width'] + + # Parse XML + tree = ET.parse(lsdx_path) + root = tree.getroot() + + # Update cluster name + for element in root.findall(".//Element[@type='cluster']"): + old_cluster = element.get('name') + element.set('name', cluster_name) + self.logger.info(f"Clustername: '{old_cluster}' → '{cluster_name}'") + if removed_strings: + self.logger.info(f" Entfernte Strings: {', '.join(removed_strings)}") + + # Update scan names with prefix + for element in root.findall(".//Element[@type='scan']"): + old_name = element.get('name') + if old_name: + match = re.match(r'^(\d+)$', old_name) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(lsd_width) + new_name = f"{scan_prefix}_{new_num_str}" + element.set('name', new_name) + self.logger.info(f"Scan-Name: '{old_name}' → '{new_name}'") + + # Update FilePath elements + for filepath in root.findall(".//FilePath"): + file_type = filepath.get('type') + text = filepath.text + + if file_type == 'lsd' and text: + match = re.match(r'^(\d+)\.lsd$', text) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(lsd_width) + new_name = f"{scan_prefix}_{new_num_str}.lsd" + filepath.text = new_name + self.logger.info(f"LSDX FilePath: {text} → {new_name}") + + elif file_type == 'preview' and text: + match = re.match(r'^Previews/(\d+)\.png$', text) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(png_width) + new_name = f"Previews/{scan_prefix}_{new_num_str}.png" + filepath.text = new_name + self.logger.info(f"LSDX FilePath: {text} → {new_name}") + + # Write back + tree.write(lsdx_path, encoding='utf-8', xml_declaration=True) + + # Add DOCTYPE back + with open(lsdx_path, 'r', encoding='utf-8') as f: + content = f.read() + + if '' not in content: + content = content.replace( + "", + "\n" + ) + with open(lsdx_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.logger.info("LSDX-Datei erfolgreich aktualisiert") + + def update_status(self, message): + """Update the status label.""" + self.status_label.config(text=message) + self.root.update_idletasks() + + +class BatchRenamer: + """Batch Renamer for processing multiple PointCab projects.""" + + def __init__(self, root, return_callback=None): + self.root = root + self.return_callback = return_callback + self.root.title("PointCab Batch Renamer") + self.root.geometry("900x700") + self.root.minsize(800, 600) + + # Variables + self.main_dir = tk.StringVar() + self.projects = [] + self.cleanup_strings = [] + self.batch_logger = None + self.processing = False + + # Load cleanup configuration + self.load_cleanup_config() + + self.setup_ui() + + def load_cleanup_config(self): + """Load cleanup strings from cluster_cleanup.txt.""" + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cluster_cleanup.txt") + self.cleanup_strings = [] + + if os.path.exists(config_path): + try: + # Use utf-8-sig to handle BOM from Windows editors + with open(config_path, 'r', encoding='utf-8-sig') as f: + for line in f: + # Strip whitespace including BOM characters + line = line.strip() + # Skip empty lines and comments + if not line: + continue + if line.startswith('#'): + continue + # Remove any remaining BOM or special chars + line = line.lstrip('\ufeff') + if line: + self.cleanup_strings.append(line) + print(f"Cleanup-Konfiguration geladen: {len(self.cleanup_strings)} Einträge") + except Exception as e: + print(f"Fehler beim Laden der Cleanup-Konfiguration: {e}") + + def clean_cluster_name(self, name): + """Remove cleanup strings from the cluster name.""" + cleaned_name = name + removed_strings = [] + + for cleanup_str in self.cleanup_strings: + if cleanup_str in cleaned_name: + removed_strings.append(cleanup_str) + cleaned_name = cleaned_name.replace(cleanup_str, '') + + return cleaned_name, removed_strings + + def setup_ui(self): + """Setup the batch renamer UI.""" + main_frame = ttk.Frame(self.root, padding="10") + main_frame.pack(fill=tk.BOTH, expand=True) + + # Back button + if self.return_callback: + back_frame = ttk.Frame(main_frame) + back_frame.pack(fill=tk.X, pady=(0, 10)) + ttk.Button(back_frame, text="← Zurück zum Hauptmenü", command=self.go_back).pack(side=tk.LEFT) + + # Directory selection + dir_frame = ttk.LabelFrame(main_frame, text="Hauptverzeichnis auswählen", padding="5") + dir_frame.pack(fill=tk.X, pady=(0, 10)) + + ttk.Entry(dir_frame, textvariable=self.main_dir, width=60).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) + ttk.Button(dir_frame, text="Durchsuchen...", command=self.select_directory).pack(side=tk.LEFT) + ttk.Button(dir_frame, text="Projekte suchen", command=self.scan_for_projects).pack(side=tk.LEFT, padx=(5, 0)) + + # Projects list + projects_frame = ttk.LabelFrame(main_frame, text="Gefundene PointCab-Projekte", padding="5") + projects_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + # Treeview for projects + columns = ('status', 'project', 'cluster', 'scans') + self.projects_tree = ttk.Treeview(projects_frame, columns=columns, show='headings', height=10) + self.projects_tree.heading('status', text='Status') + self.projects_tree.heading('project', text='Projekt') + self.projects_tree.heading('cluster', text='Clustername') + self.projects_tree.heading('scans', text='Scans') + self.projects_tree.column('status', width=100) + self.projects_tree.column('project', width=250) + self.projects_tree.column('cluster', width=150) + self.projects_tree.column('scans', width=80) + + scrollbar = ttk.Scrollbar(projects_frame, orient=tk.VERTICAL, command=self.projects_tree.yview) + self.projects_tree.configure(yscrollcommand=scrollbar.set) + + self.projects_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # Info label + self.info_label = ttk.Label(main_frame, text="Wählen Sie ein Hauptverzeichnis und klicken Sie auf 'Projekte suchen'.") + self.info_label.pack(fill=tk.X, pady=(0, 10)) + + # Progress section + progress_frame = ttk.LabelFrame(main_frame, text="Fortschritt", padding="5") + progress_frame.pack(fill=tk.X, pady=(0, 10)) + + self.progress_var = tk.DoubleVar() + self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100) + self.progress_bar.pack(fill=tk.X, pady=(0, 5)) + + self.progress_label = ttk.Label(progress_frame, text="") + self.progress_label.pack(fill=tk.X) + + # Log area + log_frame = ttk.LabelFrame(main_frame, text="Verarbeitungslog", padding="5") + log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=10, font=('Consolas', 9)) + self.log_text.pack(fill=tk.BOTH, expand=True) + self.log_text.config(state=tk.DISABLED) + + # Buttons + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=tk.X) + + self.process_btn = ttk.Button(button_frame, text="Alle verarbeiten", command=self.process_all, state=tk.DISABLED) + self.process_btn.pack(side=tk.RIGHT) + + ttk.Button(button_frame, text="Beenden", command=self.root.quit).pack(side=tk.LEFT) + + def go_back(self): + """Return to main menu.""" + if self.processing: + messagebox.showwarning("Warnung", "Bitte warten Sie, bis die Verarbeitung abgeschlossen ist.") + return + if self.return_callback: + self.return_callback() + + def select_directory(self): + """Open directory selection dialog.""" + directory = filedialog.askdirectory(title="Hauptverzeichnis auswählen") + if directory: + self.main_dir.set(directory) + + def scan_for_projects(self): + """Scan the main directory for PointCab projects.""" + main_dir = self.main_dir.get() + + if not main_dir or not os.path.isdir(main_dir): + messagebox.showerror("Fehler", "Bitte wählen Sie ein gültiges Hauptverzeichnis aus.") + return + + self.projects = [] + self.projects_tree.delete(*self.projects_tree.get_children()) + + # Scan for subdirectories with _PointCloud folder + for item in os.listdir(main_dir): + item_path = os.path.join(main_dir, item) + if os.path.isdir(item_path): + pointcloud_folder = self.find_pointcloud_folder(item_path) + if pointcloud_folder: + lsdx_file = self.find_lsdx_file(pointcloud_folder) + if lsdx_file: + scan_count = self.count_lsd_files(pointcloud_folder) + cluster_name, _ = self.clean_cluster_name(item) + + self.projects.append({ + 'path': item_path, + 'name': item, + 'pointcloud': pointcloud_folder, + 'lsdx': lsdx_file, + 'cluster_name': cluster_name, + 'scan_count': scan_count, + 'status': 'Ausstehend' + }) + + self.projects_tree.insert('', tk.END, values=( + 'Ausstehend', item, cluster_name, scan_count + )) + + if self.projects: + self.info_label.config(text=f"{len(self.projects)} PointCab-Projekte gefunden.") + self.process_btn.config(state=tk.NORMAL) + else: + self.info_label.config(text="Keine PointCab-Projekte gefunden.") + self.process_btn.config(state=tk.DISABLED) + + def find_pointcloud_folder(self, root_dir): + """Find the PointCloud folder in the root directory.""" + root_name = os.path.basename(root_dir) + expected_name = f"{root_name}_PointCloud" + + expected_path = os.path.join(root_dir, expected_name) + if os.path.isdir(expected_path): + return expected_path + + for item in os.listdir(root_dir): + item_path = os.path.join(root_dir, item) + if os.path.isdir(item_path) and item.endswith("_PointCloud"): + return item_path + + return None + + def find_lsdx_file(self, pointcloud_dir): + """Find the LSDX file in the PointCloud directory.""" + for item in os.listdir(pointcloud_dir): + if item.endswith(".lsdx"): + return os.path.join(pointcloud_dir, item) + return None + + def count_lsd_files(self, pointcloud_dir): + """Count LSD files in directory.""" + return len([f for f in os.listdir(pointcloud_dir) if f.endswith('.lsd')]) + + def setup_batch_logging(self, main_dir): + """Setup batch summary logging.""" + log_file = os.path.join(main_dir, "batch_summary.log") + + self.batch_logger = logging.getLogger('BatchRenamer') + self.batch_logger.setLevel(logging.INFO) + self.batch_logger.handlers.clear() + + fh = logging.FileHandler(log_file, encoding='utf-8') + fh.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + fh.setFormatter(formatter) + self.batch_logger.addHandler(fh) + + return log_file + + def log_message(self, message, level='info'): + """Add message to log display and batch log.""" + self.log_text.config(state=tk.NORMAL) + timestamp = datetime.now().strftime("%H:%M:%S") + self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") + self.log_text.see(tk.END) + self.log_text.config(state=tk.DISABLED) + + if self.batch_logger: + if level == 'error': + self.batch_logger.error(message) + else: + self.batch_logger.info(message) + + self.root.update_idletasks() + + def update_project_status(self, index, status): + """Update project status in treeview.""" + item_id = self.projects_tree.get_children()[index] + values = list(self.projects_tree.item(item_id)['values']) + values[0] = status + self.projects_tree.item(item_id, values=values) + self.root.update_idletasks() + + def process_all(self): + """Process all found projects.""" + if not self.projects: + messagebox.showinfo("Info", "Keine Projekte zum Verarbeiten.") + return + + result = messagebox.askyesno( + "Bestätigung", + f"Möchten Sie wirklich {len(self.projects)} Projekte verarbeiten?\n\n" + "Ein Backup der LSDX-Dateien wird automatisch erstellt." + ) + + if not result: + return + + self.processing = True + self.process_btn.config(state=tk.DISABLED) + + # Clear log + self.log_text.config(state=tk.NORMAL) + self.log_text.delete(1.0, tk.END) + self.log_text.config(state=tk.DISABLED) + + # Setup batch logging + batch_log = self.setup_batch_logging(self.main_dir.get()) + + self.log_message(f"=== Batch-Verarbeitung gestartet ===") + self.log_message(f"Hauptverzeichnis: {self.main_dir.get()}") + self.log_message(f"Anzahl Projekte: {len(self.projects)}") + self.log_message(f"Batch-Log: {batch_log}") + self.log_message("") + + success_count = 0 + error_count = 0 + + for i, project in enumerate(self.projects): + self.progress_var.set((i / len(self.projects)) * 100) + self.progress_label.config(text=f"Verarbeite {i+1}/{len(self.projects)}: {project['name']}") + self.update_project_status(i, 'Verarbeite...') + + self.log_message(f"--- Projekt: {project['name']} ---") + + try: + self.process_single_project(project) + self.update_project_status(i, '✓ Erfolgreich') + self.log_message(f"✓ {project['name']} erfolgreich verarbeitet") + success_count += 1 + except Exception as e: + self.update_project_status(i, '✗ Fehler') + self.log_message(f"✗ Fehler bei {project['name']}: {str(e)}", 'error') + error_count += 1 + + self.log_message("") + + self.progress_var.set(100) + self.progress_label.config(text="Fertig") + + # Summary + self.log_message("=== ZUSAMMENFASSUNG ===") + self.log_message(f"Gesamt: {len(self.projects)}") + self.log_message(f"Erfolgreich: {success_count}") + self.log_message(f"Fehlgeschlagen: {error_count}") + + self.processing = False + + messagebox.showinfo( + "Batch-Verarbeitung abgeschlossen", + f"Verarbeitung abgeschlossen!\n\n" + f"Erfolgreich: {success_count}\n" + f"Fehlgeschlagen: {error_count}\n\n" + f"Details siehe: {batch_log}" + ) + + def process_single_project(self, project): + """Process a single project without GUI interaction.""" + root_dir = project['path'] + pointcloud_dir = project['pointcloud'] + lsdx_file = project['lsdx'] + + # Setup project-specific logging + project_logger = logging.getLogger(f'Project_{project["name"]}') + project_logger.setLevel(logging.INFO) + project_logger.handlers.clear() + + log_file = os.path.join(root_dir, "renamer.log") + fh = logging.FileHandler(log_file, encoding='utf-8') + fh.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + fh.setFormatter(formatter) + project_logger.addHandler(fh) + + project_logger.info(f"Batch-Verarbeitung gestartet für: {root_dir}") + + # Get names + root_name = os.path.basename(root_dir) + cluster_name, removed_strings = self.clean_cluster_name(root_name) + scan_prefix = root_name + + project_logger.info(f"Clustername: {root_name} → {cluster_name}") + project_logger.info(f"Scan-Namen-Präfix: {scan_prefix}") + if removed_strings: + project_logger.info(f"Entfernte Strings: {', '.join(removed_strings)}") + + # Find preview dir + preview_dir = os.path.join(pointcloud_dir, "Previews") + if not os.path.isdir(preview_dir): + preview_dir = None + + # Get LSD files and mapping + lsd_files = [f for f in os.listdir(pointcloud_dir) if f.endswith('.lsd')] + lsd_mapping, lsd_width = self.get_file_mapping(lsd_files, 'lsd', scan_prefix) + + # Get PNG files and mapping + png_mapping = {} + png_width = lsd_width + if preview_dir: + png_files = [f for f in os.listdir(preview_dir) if f.endswith('.png')] + png_mapping, png_width = self.get_file_mapping(png_files, 'png', scan_prefix) + + # Create backup + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_name = f"{os.path.basename(lsdx_file)}.backup_{timestamp}" + backup_path = os.path.join(pointcloud_dir, backup_name) + shutil.copy2(lsdx_file, backup_path) + project_logger.info(f"Backup erstellt: {backup_name}") + + # Rename LSD files + if lsd_mapping: + self.safe_rename(pointcloud_dir, lsd_mapping, project_logger, "LSD") + + # Rename PNG files + if preview_dir and png_mapping: + self.safe_rename(preview_dir, png_mapping, project_logger, "PNG") + + # Update LSDX file + self.update_lsdx_file(lsdx_file, { + 'cluster_name': cluster_name, + 'scan_prefix': scan_prefix, + 'original_name': root_name, + 'removed_strings': removed_strings, + 'lsd_width': lsd_width, + 'png_width': png_width + }, project_logger) + + project_logger.info("Verarbeitung abgeschlossen") + + def get_file_mapping(self, file_list, extension, scan_prefix): + """Create mapping for files with full scan names.""" + files_with_nums = [] + for f in file_list: + match = re.match(r'^(\d+)\.' + extension + '$', f) + if match: + num = int(match.group(1)) + files_with_nums.append((num, f)) + + if not files_with_nums: + return {}, 2 + + files_with_nums.sort(key=lambda x: x[0]) + max_num = max(num for num, _ in files_with_nums) + + width = 3 if max_num >= 100 else 2 + + mapping = {} + for num, old_name in files_with_nums: + new_num_str = str(num).zfill(width) + new_name = f"{scan_prefix}_{new_num_str}.{extension}" + if old_name != new_name: + mapping[old_name] = new_name + + return mapping, width + + def safe_rename(self, directory, mapping, logger, file_type): + """Rename files using temporary names to avoid conflicts.""" + temp_suffix = f"_temp_{datetime.now().strftime('%Y%m%d%H%M%S')}" + + temp_mappings = [] + for old_name, new_name in mapping.items(): + src = os.path.join(directory, old_name) + if os.path.exists(src): + temp_name = src + temp_suffix + os.rename(src, temp_name) + temp_mappings.append((temp_name, os.path.join(directory, new_name))) + logger.info(f"{file_type}: {old_name} → temp") + + for temp_name, dst in temp_mappings: + os.rename(temp_name, dst) + logger.info(f"{file_type}: temp → {os.path.basename(dst)}") + + def update_lsdx_file(self, lsdx_path, params, logger): + """Update the LSDX file with new filenames, cluster name, and scan names.""" + cluster_name = params['cluster_name'] + scan_prefix = params.get('scan_prefix', params.get('original_name', cluster_name)) + removed_strings = params.get('removed_strings', []) + lsd_width = params['lsd_width'] + png_width = params['png_width'] + + tree = ET.parse(lsdx_path) + root = tree.getroot() + + # Update cluster name + for element in root.findall(".//Element[@type='cluster']"): + old_cluster = element.get('name') + element.set('name', cluster_name) + logger.info(f"Clustername: '{old_cluster}' → '{cluster_name}'") + if removed_strings: + logger.info(f" Entfernte Strings: {', '.join(removed_strings)}") + + # Update scan names with prefix + for element in root.findall(".//Element[@type='scan']"): + old_name = element.get('name') + if old_name: + match = re.match(r'^(\d+)$', old_name) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(lsd_width) + new_name = f"{scan_prefix}_{new_num_str}" + element.set('name', new_name) + logger.info(f"Scan-Name: '{old_name}' → '{new_name}'") + + # Update FilePath elements + for filepath in root.findall(".//FilePath"): + file_type = filepath.get('type') + text = filepath.text + + if file_type == 'lsd' and text: + match = re.match(r'^(\d+)\.lsd$', text) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(lsd_width) + new_name = f"{scan_prefix}_{new_num_str}.lsd" + filepath.text = new_name + logger.info(f"LSDX FilePath: {text} → {new_name}") + + elif file_type == 'preview' and text: + match = re.match(r'^Previews/(\d+)\.png$', text) + if match: + num = int(match.group(1)) + new_num_str = str(num).zfill(png_width) + new_name = f"Previews/{scan_prefix}_{new_num_str}.png" + filepath.text = new_name + logger.info(f"LSDX FilePath: {text} → {new_name}") + + tree.write(lsdx_path, encoding='utf-8', xml_declaration=True) + + # Add DOCTYPE back + with open(lsdx_path, 'r', encoding='utf-8') as f: + content = f.read() + + if '' not in content: + content = content.replace( + "", + "\n" + ) + with open(lsdx_path, 'w', encoding='utf-8') as f: + f.write(content) + + logger.info("LSDX-Datei erfolgreich aktualisiert") + + +class ProjectMerger: + """Project Merger for combining multiple PointCab projects into one.""" + + def __init__(self, root, return_callback=None): + self.root = root + self.return_callback = return_callback + self.root.title("PointCab Projektmerger") + self.root.geometry("1000x800") + self.root.minsize(900, 700) + + # Variables + self.target_dir = tk.StringVar() + self.source_dir = tk.StringVar() + self.merge_mode = tk.StringVar(value="single") + self.target_project = None + self.source_projects = [] + self.merge_logger = None + self.processing = False + + self.setup_ui() + + def setup_ui(self): + """Setup the project merger UI.""" + main_frame = ttk.Frame(self.root, padding="10") + main_frame.pack(fill=tk.BOTH, expand=True) + + # Back button + if self.return_callback: + back_frame = ttk.Frame(main_frame) + back_frame.pack(fill=tk.X, pady=(0, 10)) + ttk.Button(back_frame, text="← Zurück zum Hauptmenü", command=self.go_back).pack(side=tk.LEFT) + + # Target project selection + target_frame = ttk.LabelFrame(main_frame, text="🎯 Stammprojekt (Ziel)", padding="5") + target_frame.pack(fill=tk.X, pady=(0, 10)) + + target_entry_frame = ttk.Frame(target_frame) + target_entry_frame.pack(fill=tk.X) + ttk.Entry(target_entry_frame, textvariable=self.target_dir, width=60).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) + ttk.Button(target_entry_frame, text="Durchsuchen...", command=self.select_target).pack(side=tk.LEFT) + ttk.Button(target_entry_frame, text="Analysieren", command=self.analyze_target).pack(side=tk.LEFT, padx=(5, 0)) + + self.target_info = ttk.Label(target_frame, text="Bitte Stammprojekt auswählen", foreground="gray") + self.target_info.pack(fill=tk.X, pady=(5, 0)) + + # Mode selection + mode_frame = ttk.LabelFrame(main_frame, text="Merge-Modus", padding="5") + mode_frame.pack(fill=tk.X, pady=(0, 10)) + + ttk.Radiobutton(mode_frame, text="📄 Einzelprojekt hinzufügen", variable=self.merge_mode, + value="single", command=self.on_mode_change).pack(side=tk.LEFT, padx=(0, 20)) + ttk.Radiobutton(mode_frame, text="📂 Batch-Merge (mehrere Projekte)", variable=self.merge_mode, + value="batch", command=self.on_mode_change).pack(side=tk.LEFT) + + # Source project selection + source_frame = ttk.LabelFrame(main_frame, text="📁 Quellprojekt(e)", padding="5") + source_frame.pack(fill=tk.X, pady=(0, 10)) + + source_entry_frame = ttk.Frame(source_frame) + source_entry_frame.pack(fill=tk.X) + ttk.Entry(source_entry_frame, textvariable=self.source_dir, width=60).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) + ttk.Button(source_entry_frame, text="Durchsuchen...", command=self.select_source).pack(side=tk.LEFT) + ttk.Button(source_entry_frame, text="Projekte suchen", command=self.scan_source).pack(side=tk.LEFT, padx=(5, 0)) + + self.source_mode_label = ttk.Label(source_frame, text="Wählen Sie ein Quellprojekt aus", foreground="gray") + self.source_mode_label.pack(fill=tk.X, pady=(5, 0)) + + # Source projects list + projects_frame = ttk.LabelFrame(main_frame, text="Zu mergende Projekte", padding="5") + projects_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + columns = ('status', 'project', 'scans', 'lsd_files', 'png_files') + self.projects_tree = ttk.Treeview(projects_frame, columns=columns, show='headings', height=8) + self.projects_tree.heading('status', text='Status') + self.projects_tree.heading('project', text='Projekt') + self.projects_tree.heading('scans', text='Scans') + self.projects_tree.heading('lsd_files', text='LSD-Dateien') + self.projects_tree.heading('png_files', text='PNG-Dateien') + self.projects_tree.column('status', width=100) + self.projects_tree.column('project', width=250) + self.projects_tree.column('scans', width=80) + self.projects_tree.column('lsd_files', width=100) + self.projects_tree.column('png_files', width=100) + + scrollbar = ttk.Scrollbar(projects_frame, orient=tk.VERTICAL, command=self.projects_tree.yview) + self.projects_tree.configure(yscrollcommand=scrollbar.set) + + self.projects_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # Preview frame + preview_frame = ttk.LabelFrame(main_frame, text="Vorschau der Merge-Operationen", padding="5") + preview_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + self.preview_text = scrolledtext.ScrolledText(preview_frame, wrap=tk.WORD, height=10, font=('Consolas', 9)) + self.preview_text.pack(fill=tk.BOTH, expand=True) + self.preview_text.config(state=tk.DISABLED) + + # Progress section + progress_frame = ttk.LabelFrame(main_frame, text="Fortschritt", padding="5") + progress_frame.pack(fill=tk.X, pady=(0, 10)) + + self.progress_var = tk.DoubleVar() + self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100) + self.progress_bar.pack(fill=tk.X, pady=(0, 5)) + + self.progress_label = ttk.Label(progress_frame, text="") + self.progress_label.pack(fill=tk.X) + + # Buttons + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=tk.X) + + self.preview_btn = ttk.Button(button_frame, text="Vorschau", command=self.show_preview, state=tk.DISABLED) + self.preview_btn.pack(side=tk.RIGHT, padx=(5, 0)) + + self.merge_btn = ttk.Button(button_frame, text="🔀 Merge durchführen", command=self.execute_merge, state=tk.DISABLED) + self.merge_btn.pack(side=tk.RIGHT) + + ttk.Button(button_frame, text="Beenden", command=self.root.quit).pack(side=tk.LEFT) + + def go_back(self): + """Return to main menu.""" + if self.processing: + messagebox.showwarning("Warnung", "Bitte warten Sie, bis der Merge abgeschlossen ist.") + return + if self.return_callback: + self.return_callback() + + def on_mode_change(self): + """Handle mode change.""" + if self.merge_mode.get() == "single": + self.source_mode_label.config(text="Wählen Sie ein einzelnes Quellprojekt aus") + else: + self.source_mode_label.config(text="Wählen Sie ein Hauptverzeichnis mit mehreren Quellprojekten aus") + self.source_projects = [] + self.projects_tree.delete(*self.projects_tree.get_children()) + self.update_buttons() + + def select_target(self): + """Select target project directory.""" + directory = filedialog.askdirectory(title="Stammprojekt auswählen") + if directory: + self.target_dir.set(directory) + self.analyze_target() + + def select_source(self): + """Select source project/directory.""" + directory = filedialog.askdirectory(title="Quellprojekt(e) auswählen") + if directory: + self.source_dir.set(directory) + + def find_pointcloud_folder(self, root_dir): + """Find the PointCloud folder in the root directory.""" + root_name = os.path.basename(root_dir) + expected_name = f"{root_name}_PointCloud" + + expected_path = os.path.join(root_dir, expected_name) + if os.path.isdir(expected_path): + return expected_path + + for item in os.listdir(root_dir): + item_path = os.path.join(root_dir, item) + if os.path.isdir(item_path) and item.endswith("_PointCloud"): + return item_path + + return None + + def find_lsdx_file(self, pointcloud_dir): + """Find the LSDX file in the PointCloud directory.""" + for item in os.listdir(pointcloud_dir): + if item.endswith(".lsdx"): + return os.path.join(pointcloud_dir, item) + return None + + def count_files(self, directory, extension): + """Count files with given extension.""" + if not directory or not os.path.isdir(directory): + return 0 + return len([f for f in os.listdir(directory) if f.endswith(extension)]) + + def get_files(self, directory, extension): + """Get list of files with given extension.""" + if not directory or not os.path.isdir(directory): + return [] + return [f for f in os.listdir(directory) if f.endswith(extension)] + + def analyze_target(self): + """Analyze the target project.""" + target_dir = self.target_dir.get() + + if not target_dir or not os.path.isdir(target_dir): + messagebox.showerror("Fehler", "Bitte wählen Sie ein gültiges Stammprojekt aus.") + return + + pointcloud_dir = self.find_pointcloud_folder(target_dir) + if not pointcloud_dir: + messagebox.showerror("Fehler", "Kein PointCloud-Ordner im Stammprojekt gefunden!") + self.target_project = None + self.update_buttons() + return + + lsdx_file = self.find_lsdx_file(pointcloud_dir) + if not lsdx_file: + messagebox.showerror("Fehler", "Keine LSDX-Datei im Stammprojekt gefunden!") + self.target_project = None + self.update_buttons() + return + + preview_dir = os.path.join(pointcloud_dir, "Previews") + if not os.path.isdir(preview_dir): + preview_dir = None + + lsd_count = self.count_files(pointcloud_dir, '.lsd') + png_count = self.count_files(preview_dir, '.png') if preview_dir else 0 + + # Count scans in LSDX + scan_count = 0 + try: + tree = ET.parse(lsdx_file) + root = tree.getroot() + scan_count = len(root.findall(".//Element[@type='scan']")) + except: + pass + + self.target_project = { + 'path': target_dir, + 'name': os.path.basename(target_dir), + 'pointcloud': pointcloud_dir, + 'preview': preview_dir, + 'lsdx': lsdx_file, + 'scan_count': scan_count, + 'lsd_count': lsd_count, + 'png_count': png_count + } + + info = f"✓ {self.target_project['name']} | Scans: {scan_count} | LSD: {lsd_count} | PNG: {png_count}" + self.target_info.config(text=info, foreground="green") + self.update_buttons() + + def scan_source(self): + """Scan source directory for projects.""" + source_dir = self.source_dir.get() + + if not source_dir or not os.path.isdir(source_dir): + messagebox.showerror("Fehler", "Bitte wählen Sie ein gültiges Quellverzeichnis aus.") + return + + self.source_projects = [] + self.projects_tree.delete(*self.projects_tree.get_children()) + + if self.merge_mode.get() == "single": + # Single project mode + project = self.analyze_source_project(source_dir) + if project: + self.source_projects.append(project) + self.projects_tree.insert('', tk.END, values=( + 'Bereit', project['name'], project['scan_count'], + project['lsd_count'], project['png_count'] + )) + else: + messagebox.showerror("Fehler", "Kein gültiges PointCab-Projekt gefunden!") + else: + # Batch mode - scan subdirectories + for item in sorted(os.listdir(source_dir)): + item_path = os.path.join(source_dir, item) + if os.path.isdir(item_path): + # Skip if this is the target project + if self.target_project and os.path.samefile(item_path, self.target_project['path']): + continue + project = self.analyze_source_project(item_path) + if project: + self.source_projects.append(project) + self.projects_tree.insert('', tk.END, values=( + 'Bereit', project['name'], project['scan_count'], + project['lsd_count'], project['png_count'] + )) + + if self.source_projects: + self.source_mode_label.config(text=f"{len(self.source_projects)} Quellprojekt(e) gefunden", foreground="green") + else: + self.source_mode_label.config(text="Keine gültigen Quellprojekte gefunden", foreground="red") + + self.update_buttons() + + def analyze_source_project(self, project_dir): + """Analyze a source project and return its info.""" + pointcloud_dir = self.find_pointcloud_folder(project_dir) + if not pointcloud_dir: + return None + + lsdx_file = self.find_lsdx_file(pointcloud_dir) + if not lsdx_file: + return None + + preview_dir = os.path.join(pointcloud_dir, "Previews") + if not os.path.isdir(preview_dir): + preview_dir = None + + lsd_count = self.count_files(pointcloud_dir, '.lsd') + png_count = self.count_files(preview_dir, '.png') if preview_dir else 0 + + # Count scans in LSDX + scan_count = 0 + try: + tree = ET.parse(lsdx_file) + root = tree.getroot() + scan_count = len(root.findall(".//Element[@type='scan']")) + except: + pass + + return { + 'path': project_dir, + 'name': os.path.basename(project_dir), + 'pointcloud': pointcloud_dir, + 'preview': preview_dir, + 'lsdx': lsdx_file, + 'scan_count': scan_count, + 'lsd_count': lsd_count, + 'png_count': png_count, + 'lsd_files': self.get_files(pointcloud_dir, '.lsd'), + 'png_files': self.get_files(preview_dir, '.png') if preview_dir else [] + } + + def update_buttons(self): + """Update button states based on current selection.""" + if self.target_project and self.source_projects: + self.preview_btn.config(state=tk.NORMAL) + self.merge_btn.config(state=tk.NORMAL) + else: + self.preview_btn.config(state=tk.DISABLED) + self.merge_btn.config(state=tk.DISABLED) + + def get_conflict_resolved_name(self, filename, existing_files, suffix_counter): + """Get a conflict-resolved filename.""" + base, ext = os.path.splitext(filename) + new_name = filename + + while new_name in existing_files: + suffix_counter[0] += 1 + new_name = f"{base}_merged_{suffix_counter[0]}{ext}" + + return new_name + + def show_preview(self): + """Show preview of merge operations.""" + if not self.target_project or not self.source_projects: + return + + preview_text = "=== MERGE-VORSCHAU ===\n\n" + preview_text += f"🎯 STAMMPROJEKT: {self.target_project['name']}\n" + preview_text += f" Aktuelle Scans: {self.target_project['scan_count']}\n" + preview_text += f" LSD-Dateien: {self.target_project['lsd_count']}\n" + preview_text += f" PNG-Dateien: {self.target_project['png_count']}\n\n" + + # Get existing files in target + existing_lsd = set(self.get_files(self.target_project['pointcloud'], '.lsd')) + existing_png = set(self.get_files(self.target_project['preview'], '.png')) if self.target_project['preview'] else set() + + total_new_scans = 0 + total_new_lsd = 0 + total_new_png = 0 + + for project in self.source_projects: + preview_text += f"📁 QUELLPROJEKT: {project['name']}\n" + preview_text += f" Scans: {project['scan_count']}\n" + + # Check for LSD conflicts + lsd_conflicts = 0 + for lsd in project.get('lsd_files', []): + if lsd in existing_lsd: + lsd_conflicts += 1 + + preview_text += f" LSD-Dateien: {project['lsd_count']}" + if lsd_conflicts > 0: + preview_text += f" ({lsd_conflicts} Namenskonflikte → werden umbenannt)" + preview_text += "\n" + + # Check for PNG conflicts + png_conflicts = 0 + for png in project.get('png_files', []): + if png in existing_png: + png_conflicts += 1 + + preview_text += f" PNG-Dateien: {project['png_count']}" + if png_conflicts > 0: + preview_text += f" ({png_conflicts} Namenskonflikte → werden umbenannt)" + preview_text += "\n\n" + + total_new_scans += project['scan_count'] + total_new_lsd += project['lsd_count'] + total_new_png += project['png_count'] + + # Add to existing for next iteration + for lsd in project.get('lsd_files', []): + existing_lsd.add(lsd) + for png in project.get('png_files', []): + existing_png.add(png) + + preview_text += "=== ZUSAMMENFASSUNG ===\n" + preview_text += f"Neue Scans: {total_new_scans}\n" + preview_text += f"Neue LSD-Dateien: {total_new_lsd}\n" + preview_text += f"Neue PNG-Dateien: {total_new_png}\n" + preview_text += f"\nNach Merge:\n" + preview_text += f" Scans gesamt: {self.target_project['scan_count'] + total_new_scans}\n" + preview_text += f" LSD-Dateien: {self.target_project['lsd_count'] + total_new_lsd}\n" + preview_text += f" PNG-Dateien: {self.target_project['png_count'] + total_new_png}\n" + + preview_text += "\n⚠️ HINWEIS:\n" + preview_text += " - Ein Backup der Stammprojekt-LSDX wird erstellt\n" + preview_text += " - Bei Namenskonflikten werden Dateien mit Suffix '_merged_N' umbenannt\n" + preview_text += " - Alle Referenzen in der LSDX werden entsprechend aktualisiert\n" + + self.preview_text.config(state=tk.NORMAL) + self.preview_text.delete(1.0, tk.END) + self.preview_text.insert(tk.END, preview_text) + self.preview_text.config(state=tk.DISABLED) + + def setup_merge_logging(self, target_dir): + """Setup merge logging.""" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + log_file = os.path.join(target_dir, f"merge_{timestamp}.log") + + self.merge_logger = logging.getLogger(f'ProjectMerger_{timestamp}') + self.merge_logger.setLevel(logging.INFO) + self.merge_logger.handlers.clear() + + fh = logging.FileHandler(log_file, encoding='utf-8') + fh.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + fh.setFormatter(formatter) + self.merge_logger.addHandler(fh) + + return log_file + + def log_message(self, message, level='info'): + """Log message.""" + if self.merge_logger: + if level == 'error': + self.merge_logger.error(message) + else: + self.merge_logger.info(message) + + def update_project_status(self, index, status): + """Update project status in treeview.""" + if index < len(self.projects_tree.get_children()): + item_id = self.projects_tree.get_children()[index] + values = list(self.projects_tree.item(item_id)['values']) + values[0] = status + self.projects_tree.item(item_id, values=values) + self.root.update_idletasks() + + def execute_merge(self): + """Execute the merge operation.""" + if not self.target_project or not self.source_projects: + return + + result = messagebox.askyesno( + "Bestätigung", + f"Möchten Sie wirklich {len(self.source_projects)} Projekt(e) in das Stammprojekt mergen?\n\n" + "Ein Backup der LSDX-Datei wird automatisch erstellt." + ) + + if not result: + return + + self.processing = True + self.merge_btn.config(state=tk.DISABLED) + self.preview_btn.config(state=tk.DISABLED) + + # Setup logging + log_file = self.setup_merge_logging(self.target_project['path']) + + self.log_message("=== MERGE GESTARTET ===") + self.log_message(f"Stammprojekt: {self.target_project['name']}") + self.log_message(f"Quellprojekte: {len(self.source_projects)}") + self.log_message(f"Log-Datei: {log_file}") + + # Update preview with log + self.preview_text.config(state=tk.NORMAL) + self.preview_text.delete(1.0, tk.END) + self.preview_text.insert(tk.END, f"=== MERGE LÄUFT ===\n\nLog: {log_file}\n\n") + + try: + # Create backup of target LSDX + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_path = f"{self.target_project['lsdx']}.backup_{timestamp}" + shutil.copy2(self.target_project['lsdx'], backup_path) + self.log_message(f"Backup erstellt: {backup_path}") + self.preview_text.insert(tk.END, f"✓ Backup erstellt\n") + + # Parse target LSDX + target_tree = ET.parse(self.target_project['lsdx']) + target_root = target_tree.getroot() + target_elements = target_root.find('Elements') + + # Get existing files + existing_lsd = set(self.get_files(self.target_project['pointcloud'], '.lsd')) + existing_png = set(self.get_files(self.target_project['preview'], '.png')) if self.target_project['preview'] else set() + + # Track UUID mappings for parent references + suffix_counter = [0] + + success_count = 0 + error_count = 0 + + for i, project in enumerate(self.source_projects): + self.progress_var.set((i / len(self.source_projects)) * 100) + self.progress_label.config(text=f"Merging {i+1}/{len(self.source_projects)}: {project['name']}") + self.update_project_status(i, 'Verarbeite...') + self.root.update_idletasks() + + try: + self.merge_single_project( + project, + target_elements, + existing_lsd, + existing_png, + suffix_counter + ) + self.update_project_status(i, '✓ Erfolgreich') + self.preview_text.insert(tk.END, f"✓ {project['name']} gemerged\n") + success_count += 1 + except Exception as e: + self.update_project_status(i, '✗ Fehler') + self.preview_text.insert(tk.END, f"✗ Fehler bei {project['name']}: {e}\n") + self.log_message(f"Fehler bei {project['name']}: {e}", 'error') + error_count += 1 + + self.preview_text.see(tk.END) + self.root.update_idletasks() + + # Zähle finale Elemente vor dem Speichern + final_clusters = len(target_elements.findall("Element[@type='cluster']")) + final_scans = len(target_elements.findall("Element[@type='scan']")) + self.log_message(f"") + self.log_message(f"{'='*60}") + self.log_message(f"FINALE LSDX STATISTIK") + self.log_message(f"{'='*60}") + self.log_message(f" Cluster gesamt: {final_clusters}") + self.log_message(f" Scans gesamt: {final_scans}") + + # Save merged LSDX + target_tree.write(self.target_project['lsdx'], encoding='utf-8', xml_declaration=True) + + # Add DOCTYPE back + with open(self.target_project['lsdx'], 'r', encoding='utf-8') as f: + content = f.read() + if '' not in content: + content = content.replace( + "", + "\n" + ) + with open(self.target_project['lsdx'], 'w', encoding='utf-8') as f: + f.write(content) + + self.log_message("LSDX-Datei gespeichert") + self.log_message(f"Pfad: {self.target_project['lsdx']}") + + self.progress_var.set(100) + self.progress_label.config(text="Fertig") + + # Summary + self.log_message("=== MERGE ABGESCHLOSSEN ===") + self.log_message(f"Erfolgreich: {success_count}") + self.log_message(f"Fehlgeschlagen: {error_count}") + + self.preview_text.insert(tk.END, f"\n=== ZUSAMMENFASSUNG ===\n") + self.preview_text.insert(tk.END, f"Erfolgreich: {success_count}\n") + self.preview_text.insert(tk.END, f"Fehlgeschlagen: {error_count}\n") + self.preview_text.insert(tk.END, f"Log: {log_file}\n") + self.preview_text.config(state=tk.DISABLED) + + messagebox.showinfo( + "Merge abgeschlossen", + f"Merge abgeschlossen!\n\n" + f"Erfolgreich: {success_count}\n" + f"Fehlgeschlagen: {error_count}\n\n" + f"Details siehe: {log_file}" + ) + + except Exception as e: + self.log_message(f"Kritischer Fehler: {e}", 'error') + messagebox.showerror("Fehler", f"Fehler beim Merge:\n{e}") + + finally: + self.processing = False + self.merge_btn.config(state=tk.NORMAL) + self.preview_btn.config(state=tk.NORMAL) + + def merge_single_project(self, source_project, target_elements, existing_lsd, existing_png, suffix_counter): + """ + Merge a single source project into target. + + LSDX Struktur: + - Registration (Wurzel, parents="") + - Cluster (parents=registration_uuid, name="ClusterName") + - Scan (parents=cluster_uuid, name="1", enthält FilePath-Referenzen) + + Merge-Logik: + 1. Finde Target-Registration UUID + 2. Für jeden Cluster im Quellprojekt: + - Prüfe ob Cluster mit gleichem Namen im Ziel existiert + - Wenn ja: Verwende existierende Cluster-UUID für Scans + - Wenn nein: Füge neuen Cluster hinzu mit neuer UUID + 3. Für jeden Scan: Füge hinzu mit korrekter Cluster-UUID als Parent + """ + self.log_message(f"") + self.log_message(f"{'='*60}") + self.log_message(f"MERGE START: {source_project['name']}") + self.log_message(f"{'='*60}") + + # Parse source LSDX + source_tree = ET.parse(source_project['lsdx']) + source_root = source_tree.getroot() + source_elements = source_root.find('Elements') + + if source_elements is None: + self.log_message("FEHLER: Keine Elements in Quell-LSDX gefunden!") + raise ValueError("Keine Elements in Quell-LSDX") + + # === SCHRITT 1: Finde Target-Registration UUID === + target_registration_uuid = None + for elem in target_elements.findall("Element[@type='registration']"): + target_registration_uuid = elem.get('uuid') + self.log_message(f"[TARGET] Registration UUID: {target_registration_uuid}") + break + + if not target_registration_uuid: + self.log_message("FEHLER: Keine Registration im Ziel gefunden!") + raise ValueError("Keine Registration im Ziel-LSDX") + + # === SCHRITT 2: Sammle existierende Cluster im Target === + target_clusters = {} # name -> uuid + for elem in target_elements.findall("Element[@type='cluster']"): + cluster_name = elem.get('name', '') + cluster_uuid = elem.get('uuid') + target_clusters[cluster_name] = cluster_uuid + self.log_message(f"[TARGET] Existierender Cluster: '{cluster_name}' UUID={cluster_uuid}") + + self.log_message(f"[TARGET] Cluster gesamt: {len(target_clusters)}") + + # === SCHRITT 3: Analysiere Source-Struktur === + source_registration_uuid = None + source_clusters = {} # source_uuid -> {name, scans: []} + source_scans = [] + + for elem in source_elements.findall("Element"): + elem_type = elem.get('type') + elem_uuid = elem.get('uuid') + elem_name = elem.get('name', '') + elem_parents = elem.get('parents', '') + + if elem_type == 'registration': + source_registration_uuid = elem_uuid + self.log_message(f"[SOURCE] Registration UUID: {elem_uuid}") + elif elem_type == 'cluster': + source_clusters[elem_uuid] = {'name': elem_name, 'parent': elem_parents, 'scans': []} + self.log_message(f"[SOURCE] Cluster: '{elem_name}' UUID={elem_uuid} parent={elem_parents}") + elif elem_type == 'scan': + source_scans.append({'elem': elem, 'uuid': elem_uuid, 'name': elem_name, 'parent': elem_parents}) + self.log_message(f"[SOURCE] Scan: '{elem_name}' UUID={elem_uuid} parent={elem_parents}") + + self.log_message(f"[SOURCE] Cluster: {len(source_clusters)}, Scans: {len(source_scans)}") + + # === SCHRITT 4: Ordne Scans den Clustern zu === + for scan in source_scans: + scan_parent = scan['parent'] + if scan_parent in source_clusters: + source_clusters[scan_parent]['scans'].append(scan) + else: + self.log_message(f"WARNUNG: Scan '{scan['name']}' hat unbekannten Parent: {scan_parent}") + + # === SCHRITT 5: Dateien kopieren === + lsd_renames = {} # old_name -> new_name + png_renames = {} # old_name -> new_name + + self.log_message(f"") + self.log_message(f"--- Kopiere LSD-Dateien ---") + for lsd_file in source_project.get('lsd_files', []): + src_path = os.path.join(source_project['pointcloud'], lsd_file) + new_name = self.get_conflict_resolved_name(lsd_file, existing_lsd, suffix_counter) + dst_path = os.path.join(self.target_project['pointcloud'], new_name) + + if os.path.exists(src_path): + shutil.copy2(src_path, dst_path) + existing_lsd.add(new_name) + if lsd_file != new_name: + lsd_renames[lsd_file] = new_name + self.log_message(f" LSD kopiert (umbenannt): {lsd_file} → {new_name}") + else: + self.log_message(f" LSD kopiert: {lsd_file}") + + self.log_message(f"--- Kopiere PNG-Dateien ---") + if source_project['preview'] and self.target_project['preview']: + if not os.path.exists(self.target_project['preview']): + os.makedirs(self.target_project['preview']) + + for png_file in source_project.get('png_files', []): + src_path = os.path.join(source_project['preview'], png_file) + new_name = self.get_conflict_resolved_name(png_file, existing_png, suffix_counter) + dst_path = os.path.join(self.target_project['preview'], new_name) + + if os.path.exists(src_path): + shutil.copy2(src_path, dst_path) + existing_png.add(new_name) + if png_file != new_name: + png_renames[png_file] = new_name + self.log_message(f" PNG kopiert (umbenannt): {png_file} → {new_name}") + else: + self.log_message(f" PNG kopiert: {png_file}") + + # === SCHRITT 6: Cluster und Scans hinzufügen === + self.log_message(f"") + self.log_message(f"--- Füge Cluster und Scans hinzu ---") + + cluster_uuid_mapping = {} # source_cluster_uuid -> target_cluster_uuid + clusters_added = 0 + clusters_reused = 0 + scans_added = 0 + + for source_cluster_uuid, cluster_info in source_clusters.items(): + cluster_name = cluster_info['name'] + + # Prüfe ob Cluster mit gleichem Namen im Ziel existiert + if cluster_name in target_clusters: + # Cluster existiert bereits - verwende existierende UUID + target_cluster_uuid = target_clusters[cluster_name] + cluster_uuid_mapping[source_cluster_uuid] = target_cluster_uuid + self.log_message(f" CLUSTER EXISTIERT: '{cluster_name}' → verwende UUID {target_cluster_uuid}") + clusters_reused += 1 + else: + # Neuen Cluster hinzufügen + new_cluster_uuid = "{" + str(uuid.uuid4()) + "}" + cluster_uuid_mapping[source_cluster_uuid] = new_cluster_uuid + + # Finde das Cluster-Element im Source + for elem in source_elements.findall("Element[@type='cluster']"): + if elem.get('uuid') == source_cluster_uuid: + new_cluster = copy.deepcopy(elem) + new_cluster.set('uuid', new_cluster_uuid) + new_cluster.set('parents', target_registration_uuid) # Parent = Target-Registration + target_elements.append(new_cluster) + + # Merke für zukünftige Projekte + target_clusters[cluster_name] = new_cluster_uuid + + self.log_message(f" CLUSTER HINZUGEFÜGT: '{cluster_name}' UUID={new_cluster_uuid} parent={target_registration_uuid}") + clusters_added += 1 + break + + # Füge Scans dieses Clusters hinzu + target_cluster_uuid = cluster_uuid_mapping[source_cluster_uuid] + for scan_info in cluster_info['scans']: + scan_elem = scan_info['elem'] + new_scan_uuid = "{" + str(uuid.uuid4()) + "}" + + new_scan = copy.deepcopy(scan_elem) + new_scan.set('uuid', new_scan_uuid) + new_scan.set('parents', target_cluster_uuid) # Parent = Cluster-UUID + + # Update FilePath references + for filepath in new_scan.findall(".//FilePath"): + file_type = filepath.get('type') + text = filepath.text or '' + + if file_type == 'lsd' and text: + if text in lsd_renames: + old_text = text + filepath.text = lsd_renames[text] + self.log_message(f" FilePath lsd: {old_text} → {filepath.text}") + + elif file_type == 'preview' and text: + if text.startswith("Previews/"): + png_name = text[9:] + if png_name in png_renames: + old_text = text + filepath.text = f"Previews/{png_renames[png_name]}" + self.log_message(f" FilePath preview: {old_text} → {filepath.text}") + + target_elements.append(new_scan) + self.log_message(f" SCAN HINZUGEFÜGT: '{scan_info['name']}' UUID={new_scan_uuid} parent={target_cluster_uuid}") + scans_added += 1 + + # === ZUSAMMENFASSUNG === + self.log_message(f"") + self.log_message(f"--- MERGE ZUSAMMENFASSUNG ---") + self.log_message(f" Cluster hinzugefügt: {clusters_added}") + self.log_message(f" Cluster wiederverwendet: {clusters_reused}") + self.log_message(f" Scans hinzugefügt: {scans_added}") + self.log_message(f" LSD-Dateien kopiert: {len(source_project.get('lsd_files', []))}") + self.log_message(f" PNG-Dateien kopiert: {len(source_project.get('png_files', []))}") + self.log_message(f"{'='*60}") + + +class MainMenu: + """Main menu for selecting between Single Project, Batch mode, and Project Merger.""" + + def __init__(self, root): + self.root = root + self.root.title("PointCab Projekt Umbenenner v4.1") + self.root.geometry("500x450") + self.root.resizable(False, False) + + self.current_frame = None + self.setup_menu() + + def setup_menu(self): + """Setup the main menu.""" + self.clear_window() + self.root.geometry("500x450") + self.root.resizable(False, False) + + main_frame = ttk.Frame(self.root, padding="30") + main_frame.pack(fill=tk.BOTH, expand=True) + + # Title + title_label = ttk.Label(main_frame, text="PointCab Projekt Umbenenner", font=('Helvetica', 18, 'bold')) + title_label.pack(pady=(0, 5)) + + version_label = ttk.Label(main_frame, text="Version 4.1", font=('Helvetica', 10), foreground='gray') + version_label.pack(pady=(0, 30)) + + # Description + desc_label = ttk.Label( + main_frame, + text="Wählen Sie einen Modus:", + font=('Helvetica', 11) + ) + desc_label.pack(pady=(0, 20)) + + # Buttons frame + buttons_frame = ttk.Frame(main_frame) + buttons_frame.pack(fill=tk.X, pady=10) + + # Style for big buttons + style = ttk.Style() + style.configure('Big.TButton', font=('Helvetica', 12), padding=15) + + # Single project button + single_btn = ttk.Button( + buttons_frame, + text="📁 Einzelprojekt bearbeiten", + style='Big.TButton', + command=self.open_single_project + ) + single_btn.pack(fill=tk.X, pady=5) + + single_desc = ttk.Label( + buttons_frame, + text="Ein einzelnes PointCab-Projekt auswählen und verarbeiten", + foreground='gray' + ) + single_desc.pack(pady=(0, 15)) + + # Batch button + batch_btn = ttk.Button( + buttons_frame, + text="📂 Batch Renamer", + style='Big.TButton', + command=self.open_batch_renamer + ) + batch_btn.pack(fill=tk.X, pady=5) + + batch_desc = ttk.Label( + buttons_frame, + text="Mehrere Projekte in einem Hauptverzeichnis automatisch verarbeiten", + foreground='gray' + ) + batch_desc.pack(pady=(0, 15)) + + # Merger button + merger_btn = ttk.Button( + buttons_frame, + text="🔀 Projektmerger", + style='Big.TButton', + command=self.open_project_merger + ) + merger_btn.pack(fill=tk.X, pady=5) + + merger_desc = ttk.Label( + buttons_frame, + text="Mehrere Projekte in ein Stammprojekt zusammenführen", + foreground='gray' + ) + merger_desc.pack(pady=(0, 15)) + + # Exit button + ttk.Button(main_frame, text="Beenden", command=self.root.quit).pack(pady=(20, 0)) + + def clear_window(self): + """Clear all widgets from window.""" + for widget in self.root.winfo_children(): + widget.destroy() + + def open_single_project(self): + """Open single project mode.""" + self.clear_window() + self.root.geometry("850x750") + self.root.resizable(True, True) + PointCabRenamer(self.root, return_callback=self.setup_menu) + + def open_batch_renamer(self): + """Open batch renamer mode.""" + self.clear_window() + self.root.geometry("900x700") + self.root.resizable(True, True) + BatchRenamer(self.root, return_callback=self.setup_menu) + + def open_project_merger(self): + """Open project merger mode.""" + self.clear_window() + self.root.geometry("1000x800") + self.root.resizable(True, True) + ProjectMerger(self.root, return_callback=self.setup_menu) + + +def main(): + """Main entry point.""" + root = tk.Tk() + app = MainMenu(root) + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/release/PointCab_Renamer_v4.2.1/requirements.txt b/release/PointCab_Renamer_v4.2.1/requirements.txt new file mode 100644 index 0000000..2b2d4b1 --- /dev/null +++ b/release/PointCab_Renamer_v4.2.1/requirements.txt @@ -0,0 +1,21 @@ +# PointCab Renamer v4.1 - Python Dependencies +# ========================================== +# +# Diese Datei listet alle Python-Abhängigkeiten auf. +# Installation: pip install -r requirements.txt +# +# Hinweis: tkinter ist Teil der Python-Standardbibliothek +# und muss unter Ubuntu separat installiert werden: +# sudo apt install python3-tk + +# Für Build/Deployment: +pyinstaller>=5.0 + +# Standardbibliotheken (keine Installation nötig): +# - tkinter (GUI) +# - xml.etree.ElementTree (LSDX-Parsing) +# - uuid (UUID-Generierung) +# - shutil (Dateioperationen) +# - os (Betriebssystemfunktionen) +# - datetime (Zeitstempel) +# - logging (Protokollierung) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2b2d4b1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,21 @@ +# PointCab Renamer v4.1 - Python Dependencies +# ========================================== +# +# Diese Datei listet alle Python-Abhängigkeiten auf. +# Installation: pip install -r requirements.txt +# +# Hinweis: tkinter ist Teil der Python-Standardbibliothek +# und muss unter Ubuntu separat installiert werden: +# sudo apt install python3-tk + +# Für Build/Deployment: +pyinstaller>=5.0 + +# Standardbibliotheken (keine Installation nötig): +# - tkinter (GUI) +# - xml.etree.ElementTree (LSDX-Parsing) +# - uuid (UUID-Generierung) +# - shutil (Dateioperationen) +# - os (Betriebssystemfunktionen) +# - datetime (Zeitstempel) +# - logging (Protokollierung)