├── .gitignore ├── LICENSE ├── README.md ├── RobotGeneralController.png ├── img ├── +2_16x16.ico ├── +2_32x32.ico ├── +3_16x16.ico ├── +3_32x32.ico ├── +4_16x16.ico ├── +4_32x32.ico ├── +_16x16.ico ├── +_32x32.ico ├── -2_16x16.ico ├── -2_32x32.ico ├── -_16x16.ico ├── -_32x32.ico ├── favicon.ico ├── favicon_64.ico ├── pencil_16x16.ico ├── pencil_32x32.ico ├── png │ ├── +.png │ ├── +2.png │ ├── +3.png │ ├── +4.png │ ├── -.png │ ├── -2.png │ ├── favicon.png │ ├── pencil.png │ ├── running.png │ ├── running1.png │ ├── setting.png │ ├── setting2.png │ ├── stop.png │ └── yes.png ├── running1_16x16.ico ├── running1_32x32.ico ├── running_16x16.ico ├── running_32x32.ico ├── setting2_16x16.ico ├── setting2_32x32.ico ├── setting_16x16.ico ├── setting_32x32.ico ├── stop_16x16.ico ├── stop_32x32.ico ├── yes_16x16.ico └── yes_32x32.ico ├── model ├── Hexapod.mo └── tmp.mo └── src ├── Controller.py ├── WindowElement.py ├── configurator.py ├── file_trans_manager.py ├── init.cfg ├── language ├── language.bak ├── massagehead.py ├── robotsocket.py ├── test.py └── tkutils.py /.gitignore: -------------------------------------------------------------------------------- 1 | src/venv/ 2 | src/.idea/ 3 | src/__pycache__/ 4 | src/build/ 5 | src/dist/ 6 | src/Controller.spec -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RobotGeneralController 2 | #### 软件名称 3 | 4 | 通用机器人控制器上位机。(以下简称`上位机`) 5 | 6 | 7 | 8 | #### 上位机介绍 9 | 10 | 本软件为通用机器人控制器的上位机程序。 11 | 12 | ###### 具有以下特点: 13 | 1. 支持跨平台。目前已提供`Windows`、`Mac OS`、`Linux`系统的可运行程序。 14 | 2. 支持多国语言。目前已提供中、英、法、德、韩五中语言(存放在language语言包中)。 15 | 3. 支持各类外形的机器人。通过模块化组装的使用方式。 16 | 4. 集成常用的数据调试接口。如串口、TCP网口。 17 | 5. 此后将支持动态解析python脚本,以便使用人员个性化适配个人机器人。 18 | 19 | ![](https://gitee.com/ClimbSnailQ/Project_Image/raw/master/RobotGeneralController/RobotGeneralController.png) 20 | 21 | ![RobotGeneralController.png](./RobotGeneralController.png) 22 | 23 | 24 | #### 上位机实现方案 25 | 26 | 使用`python`开发,基于`tkinter`绘制页面。最后并使用`pyinstaller`打包成对应平台的可执行程序。 27 | 28 | 1. 使用python编写`pip3 install pillow pyinstaller -i https://mirrors.aliyun.com/pypi/simple/` 29 | 2. Linux下PIL安装`sudo apt-get install python3-pil python3-pil.imagetk` 30 | 3. GUI使用python下的tkinter 31 | 4. 使用pyinstaller打包程序 `pyinstaller --icon ./img/favicon.ico -w -F Controller.py configurator.py WindowElement.py tkutils.py massagehead.py` 32 | 33 | 34 | #### 文件介绍 35 | 1. src -> 源代码文件夹 36 | * `Controller.py` _为上位机的主程序控制器_ 37 | * `WindowElement.py` _为GUI界面封装的子控件元素类的集合_ 38 | * `massagehead.py` _为引擎与各个控件之间的消息头_ 39 | * `configurator.py` _为处理配置文件类包_ 40 | * `robotsocket.py` _为socket通信封装的服务器与客户端类_ 41 | * `file_trans_manager.py` _基于robotsocket通信封装的文件传输管理类_ 42 | * `init.cfg` _程序总配置文件_ 43 | * `language` _程序语言包文件(定义了系统语言字体相关的信息)_ 44 | 2. img -> 存放本软件涉及到的图标 45 | * 注:更改路径可以在`init.cfg`文件中system_init/imagepath下改 46 | 3. model -> 模型文件夹 47 | * `Hexapod.mo` _为六足机器人的预设模型_ 48 | * `tmp.mo` _为测试模型_ 49 | 4. action -> 动作组文件夹 50 | * 本目录暂时为空 51 | 52 | #### 致谢 53 | 1. tkinter的Frame窗体设计参考了 54 | 55 | -------------------------------------------------------------------------------- /RobotGeneralController.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/RobotGeneralController.png -------------------------------------------------------------------------------- /img/+2_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/+2_16x16.ico -------------------------------------------------------------------------------- /img/+2_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/+2_32x32.ico -------------------------------------------------------------------------------- /img/+3_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/+3_16x16.ico -------------------------------------------------------------------------------- /img/+3_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/+3_32x32.ico -------------------------------------------------------------------------------- /img/+4_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/+4_16x16.ico -------------------------------------------------------------------------------- /img/+4_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/+4_32x32.ico -------------------------------------------------------------------------------- /img/+_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/+_16x16.ico -------------------------------------------------------------------------------- /img/+_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/+_32x32.ico -------------------------------------------------------------------------------- /img/-2_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/-2_16x16.ico -------------------------------------------------------------------------------- /img/-2_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/-2_32x32.ico -------------------------------------------------------------------------------- /img/-_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/-_16x16.ico -------------------------------------------------------------------------------- /img/-_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/-_32x32.ico -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/favicon.ico -------------------------------------------------------------------------------- /img/favicon_64.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/favicon_64.ico -------------------------------------------------------------------------------- /img/pencil_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/pencil_16x16.ico -------------------------------------------------------------------------------- /img/pencil_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/pencil_32x32.ico -------------------------------------------------------------------------------- /img/png/+.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/+.png -------------------------------------------------------------------------------- /img/png/+2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/+2.png -------------------------------------------------------------------------------- /img/png/+3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/+3.png -------------------------------------------------------------------------------- /img/png/+4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/+4.png -------------------------------------------------------------------------------- /img/png/-.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/-.png -------------------------------------------------------------------------------- /img/png/-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/-2.png -------------------------------------------------------------------------------- /img/png/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/favicon.png -------------------------------------------------------------------------------- /img/png/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/pencil.png -------------------------------------------------------------------------------- /img/png/running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/running.png -------------------------------------------------------------------------------- /img/png/running1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/running1.png -------------------------------------------------------------------------------- /img/png/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/setting.png -------------------------------------------------------------------------------- /img/png/setting2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/setting2.png -------------------------------------------------------------------------------- /img/png/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/stop.png -------------------------------------------------------------------------------- /img/png/yes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/png/yes.png -------------------------------------------------------------------------------- /img/running1_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/running1_16x16.ico -------------------------------------------------------------------------------- /img/running1_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/running1_32x32.ico -------------------------------------------------------------------------------- /img/running_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/running_16x16.ico -------------------------------------------------------------------------------- /img/running_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/running_32x32.ico -------------------------------------------------------------------------------- /img/setting2_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/setting2_16x16.ico -------------------------------------------------------------------------------- /img/setting2_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/setting2_32x32.ico -------------------------------------------------------------------------------- /img/setting_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/setting_16x16.ico -------------------------------------------------------------------------------- /img/setting_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/setting_32x32.ico -------------------------------------------------------------------------------- /img/stop_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/stop_16x16.ico -------------------------------------------------------------------------------- /img/stop_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/stop_32x32.ico -------------------------------------------------------------------------------- /img/yes_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/yes_16x16.ico -------------------------------------------------------------------------------- /img/yes_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClimbSnail/RobotGeneralController/cd092d3553e50a9bcfc035e607285d4f543e3a35/img/yes_32x32.ico -------------------------------------------------------------------------------- /model/Hexapod.mo: -------------------------------------------------------------------------------- 1 | { 2 | "model_name_list": "S0 S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 S13 S14 S15 S16 S17", 3 | "S0": { 4 | "pos_x": 204.0, 5 | "pos_y": 19.5, 6 | "model_en": true, 7 | "val": "1500" 8 | }, 9 | "S1": { 10 | "pos_x": 260.0, 11 | "pos_y": 70.0, 12 | "model_en": true, 13 | "val": "1500" 14 | }, 15 | "S2": { 16 | "pos_x": 293.0, 17 | "pos_y": 121.5, 18 | "model_en": true, 19 | "val": "1500" 20 | }, 21 | "S3": { 22 | "pos_x": 97.0, 23 | "pos_y": 172.0, 24 | "model_en": true, 25 | "val": "1500" 26 | }, 27 | "S4": { 28 | "pos_x": 205.0, 29 | "pos_y": 172.5, 30 | "model_en": true, 31 | "val": "1500" 32 | }, 33 | "S5": { 34 | "pos_x": 314.0, 35 | "pos_y": 172.5, 36 | "model_en": true, 37 | "val": "1500" 38 | }, 39 | "S6": { 40 | "pos_x": 195.0, 41 | "pos_y": 324.0, 42 | "model_en": true, 43 | "val": "1500" 44 | }, 45 | "S7": { 46 | "pos_x": 239.0, 47 | "pos_y": 277.0, 48 | "model_en": true, 49 | "val": "1500" 50 | }, 51 | "S8": { 52 | "pos_x": 298.0, 53 | "pos_y": 224.0, 54 | "model_en": true, 55 | "val": "1500" 56 | }, 57 | "S9": { 58 | "pos_x": 543.0, 59 | "pos_y": 23.0, 60 | "model_en": true, 61 | "val": "1500" 62 | }, 63 | "S10": { 64 | "pos_x": 486.0, 65 | "pos_y": 71.5, 66 | "model_en": true, 67 | "val": "1500" 68 | }, 69 | "S11": { 70 | "pos_x": 446.0, 71 | "pos_y": 118.5, 72 | "model_en": true, 73 | "val": "1500" 74 | }, 75 | "S12": { 76 | "pos_x": 648.0, 77 | "pos_y": 171.5, 78 | "model_en": true, 79 | "val": "1500" 80 | }, 81 | "S13": { 82 | "pos_x": 537.0, 83 | "pos_y": 170.5, 84 | "model_en": true, 85 | "val": "1500" 86 | }, 87 | "S14": { 88 | "pos_x": 424.0, 89 | "pos_y": 172.0, 90 | "model_en": true, 91 | "val": "1500" 92 | }, 93 | "S15": { 94 | "pos_x": 438.0, 95 | "pos_y": 225.0, 96 | "model_en": true, 97 | "val": "1500" 98 | }, 99 | "S16": { 100 | "pos_x": 494.0, 101 | "pos_y": 276.5, 102 | "model_en": true, 103 | "val": "1500" 104 | }, 105 | "S17": { 106 | "pos_x": 547.0, 107 | "pos_y": 328.0, 108 | "model_en": true, 109 | "val": "1500" 110 | } 111 | } -------------------------------------------------------------------------------- /model/tmp.mo: -------------------------------------------------------------------------------- 1 | { 2 | "model_name_list": "S0 S1 S2 S3", 3 | "S0": { 4 | "pos_x": 50, 5 | "pos_y": 50, 6 | "model_en": true, 7 | "val": "1500" 8 | }, 9 | "S1": { 10 | "pos_x": 89.0, 11 | "pos_y": 187.5, 12 | "model_en": true, 13 | "val": "1500" 14 | }, 15 | "S2": { 16 | "pos_x": 362.0, 17 | "pos_y": 220.0, 18 | "model_en": true, 19 | "val": "1500" 20 | }, 21 | "S3": { 22 | "pos_x": 224.0, 23 | "pos_y": 98.0, 24 | "model_en": true, 25 | "val": "1500" 26 | } 27 | } -------------------------------------------------------------------------------- /src/Controller.py: -------------------------------------------------------------------------------- 1 | import configurator as cfg 2 | import tkinter as tk 3 | import tkutils as tku 4 | import massagehead as mh 5 | import WindowElement as we 6 | from tkinter import messagebox 7 | 8 | VERSION = "Ver1.0" 9 | 10 | class Engine(object): 11 | """ 12 | 引擎 13 | """ 14 | def __init__(self, root): 15 | """ 16 | 引擎初始化 17 | :param root:窗体控件 18 | """ 19 | self.root = root 20 | self.root.protocol("WM_DELETE_WINDOW", self.on_closing) 21 | self.v_lang = [] # 储存语言项 22 | self.word_map = {} # 储存指定语言所对应字段下的文字 23 | self.start() 24 | self.OnThreadMessage(mh.M_ENGINE, mh.M_SYSINFO, mh.A_INFO_PRINT, "System init succ.\n") 25 | 26 | def start(self ): 27 | """ 28 | 使用grid管理位置 29 | :return: 30 | """ 31 | # 初始化系统配置信息 32 | self.m_sysinfo_file = cfg.SystemConfig("./init.cfg") 33 | self.m_sys_info = self.m_sysinfo_file.m_sysinfo 34 | # 模型文件控制对象 35 | self.m_model_file = cfg.ModelFile() 36 | # 动作组文件控制对象 37 | self.m_action_file = cfg.ActionFile() 38 | # 初始化语言控制类 39 | self.m_lang = cfg.Language(self.m_sys_info["system_init"]["language"]) # first 40 | self.v_lang, self.word_map = self.m_lang.api(mh.A_UPDATALANG) # 先执行一遍语言更新 41 | 42 | # 初始化菜单栏 43 | self.m_menu = we.CtrlMenu(self.root, self) 44 | 45 | self.root.iconbitmap(self.m_sys_info["system_init"]["imagepath"] + "favicon_64.ico") # 窗体图标 46 | 47 | # 初始化舵机控件管理器 48 | self.madel_grid_frame = tk.Frame(self.root, width=800, height=450, bg="white") 49 | #self.madel_grid_frame.grid(row=0, column=0) 50 | self.madel_grid_frame.place(x=0, y=0) 51 | #self.madel_grid_frame.pack() 52 | self.madel_grid_frame.update() 53 | self.m_modelManager = we.ModelManager(self.madel_grid_frame, self, 54 | width=self.madel_grid_frame.winfo_width(), 55 | height=self.madel_grid_frame.winfo_height()) 56 | 57 | # 连接器相关控件 58 | # 使用LabelFrame控件 框出连接相关的控件 59 | self.connor_grid_frame = tk.LabelFrame(self.root, text=self.word_map["Connect"]["Connect"], 60 | labelanchor="nw", bg="white") 61 | #self.connor_grid_frame.place(anchor="ne", relx=1.0, rely=0.0) 62 | #self.connor_grid_frame.grid(row=0, column=1) 63 | self.connor_grid_frame.place(x=self.madel_grid_frame.winfo_width()+5, y=0) 64 | self.connor_grid_frame.update() 65 | self.m_connor = we.Connector(self.connor_grid_frame, self) 66 | 67 | # 日志打印类 68 | # 日志打印信息框 69 | self.m_info_out = tk.LabelFrame(self.root, text=self.word_map["SysOut"]["title"], 70 | labelanchor="nw", width=20, height=30) 71 | #self.m_info_out.place(anchor="ne", relx=1.0, rely=0.0) 72 | #self.info_grid_frame.grid(row=0, column=1) 73 | self.m_info_out.update() 74 | self.m_info_out.place(x=self.madel_grid_frame.winfo_width()+5, 75 | y=self.connor_grid_frame.winfo_height()+5) 76 | self.m_infoOut = we.InfoOut(self.m_info_out, self) 77 | 78 | # 动作组可视表格 79 | self.action_grid_frame = tk.Frame(self.root, width=800, height=150, bg="white") 80 | #self.action_grid_frame.grid(row=0, column=0) 81 | #self.action_grid_frame.place(x=0, y=self.madel_grid_frame.winfo_height()) 82 | self.action_grid_frame.pack(side=tk.BOTTOM, expand='no', anchor=tk.SW) 83 | self.action_grid_frame.update() 84 | self.m_actionTable = we.GroupActionTable(self.action_grid_frame, self, 85 | width=self.action_grid_frame.winfo_width(), 86 | height=self.action_grid_frame.winfo_height()) 87 | 88 | def OnThreadMessage(self, fromwho, towho, action, param = None): 89 | """ 90 | 引擎的调度函数 控件利用此函数可间接操作或者获取其他控件的对应资源 91 | :param fromwho:表示调用者 92 | :param towho:表示请求操作的控件 93 | :param action:表示请求操作的操作类型 94 | :param param:操作请求所携带的参数(根据具体请求来指定参数类型) 95 | """ 96 | print(fromwho, towho, action, param) 97 | #info = fromwho+" "+towho+" "+action+" "+param 98 | #self.OnThreadMessage(mh.M_ENGINE, mh.M_SYSINFO, mh.A_INFO_PRINT, info+"\n") 99 | 100 | if towho == mh.M_LANGUAGE and action == mh.A_UPDATALANG: # 语言更新请求 101 | self.v_lang, self.word_map = self.m_lang.api(action, param) 102 | self.m_menu.api(mh.A_UPDATALANG) # 菜单栏语言更新 103 | self.m_infoOut.api(mh.A_UPDATALANG) 104 | self.m_modelManager.api(mh.A_UPDATALANG) # 按钮语言更新 105 | self.updata_lang(self.word_map) 106 | 107 | elif towho == mh.M_SMMODEL_MANAGER: # 舵机模型控件操作请求 108 | self.m_modelManager.api(action, param) 109 | elif towho == mh.M_SYSINFO: 110 | self.m_infoOut.api(mh.A_INFO_PRINT, param) 111 | # 模型文件读写操作 112 | elif towho == mh.M_MODEL_FILEMANAGER: 113 | self.m_infoOut.api(mh.A_INFO_PRINT, action+" "+param+"\n") 114 | if action == mh.A_FILE_CREATE: 115 | model_filepath = param 116 | self.m_model_file.create_file(model_filepath) 117 | self.m_modelManager.api(mh.A_SET_MODELNAME, model_filepath.split('/')[-1]) 118 | elif action == mh.A_FILE_OPEN: 119 | model_filepath = param 120 | model_info = self.m_model_file.read(model_filepath) 121 | self.m_modelManager.api(mh.A_SET_MODELINFO, model_info) 122 | self.m_modelManager.api(mh.A_SET_MODELNAME, model_filepath.split('/')[-1]) 123 | self.m_actionTable.api(mh.A_SET_TABLEHEAD, model_info) 124 | elif action == mh.A_FILE_SAVE: 125 | model_info = self.m_modelManager.api(mh.A_GET_MODELINFO, None) 126 | self.m_model_file.save(model_info) 127 | elif action == mh.A_FILE_SAVEAS: 128 | model_filepath = param 129 | model_info = self.m_modelManager.api(mh.A_GET_MODELINFO, None) 130 | self.m_model_file.save(model_info, model_filepath) 131 | self.m_modelManager.api(mh.A_SET_MODELNAME, model_filepath.split('/')[-1]) 132 | # 动作组文件读写 133 | elif towho == mh.M_ACTION_FILEMANAGER: 134 | self.m_infoOut.api(mh.A_INFO_PRINT, action+" "+param+"\n") 135 | if action == mh.A_FILE_CREATE: 136 | action_filepath = param 137 | self.m_action_file.create_file(action_filepath) 138 | elif action == mh.A_FILE_OPEN: 139 | action_filepath = param 140 | action_info = self.m_model_file.read(action_filepath) 141 | self.m_modelManager.api(mh.A_SET_ACTIONINFO, action_info) 142 | elif action == mh.A_FILE_SAVE: 143 | action_info = self.m_modelManager.api(mh.A_GET_ACTIONINFO, None) 144 | self.m_action_file.save(action_info) 145 | elif action == mh.A_FILE_SAVEAS: 146 | action_filepath = param 147 | action_info = self.m_modelManager.api(mh.A_GET_ACTIONINFO, None) 148 | self.m_action_file.save(action_info, action_filepath) 149 | # 模型数据呈现指定数据 150 | elif action == mh.A_UPDATA_ALL_MODEL_VAL: 151 | self.m_modelManager.api(mh.A_UPDATA_ALL_MODEL_VAL, param) 152 | # 表格数据呈现指定数据 153 | elif action == mh.A_UPDATA_TREE_SELECT_ROW: 154 | self.m_actionTable.api(mh.A_UPDATA_TREE_SELECT_ROW, param) 155 | 156 | def updata_lang(self, val_map): 157 | """ 158 | 更新主窗口字段语种 159 | :param val_map: 字段信息所对应要显示的语言 160 | :return: None 161 | """ 162 | self.root.title(self.word_map["Windows"]["Title"]+"\t "+VERSION) # 窗口名 163 | self.connor_grid_frame["text"] = val_map["Connect"]["Connect"] 164 | self.m_info_out["text"] = val_map["SysOut"]["title"] 165 | 166 | def on_closing(self): 167 | """ 168 | 关闭主窗口时要触发的函数 169 | :return: None 170 | """ 171 | if messagebox.askokcancel("Quit", "Do you want to quit?"): 172 | self.m_modelManager.__del__() 173 | root.destroy() 174 | 175 | if __name__ == "__main__": 176 | root = tk.Tk() # 创建窗口对象的背景色 177 | root.title("机器人通用控制平台"+"\t "+VERSION) #窗口名 178 | root.geometry('1000x655+10+10') 179 | root.resizable(False, False) # 设置窗体不可改变大小 180 | engine = Engine(root) 181 | tku.center_window(root) # 将窗体移动到屏幕中央 182 | # 进入消息循环 父窗口进入事件循环,可以理解为保持窗口运行,否则界面不展示 183 | root.mainloop() -------------------------------------------------------------------------------- /src/WindowElement.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import tkinter as tk 4 | import tkutils as tku 5 | from tkinter import ttk 6 | import massagehead as mh 7 | import tkinter.font as tkFont 8 | from tkinter import messagebox 9 | from tkinter import simpledialog 10 | from tkinter import filedialog 11 | 12 | from PIL import Image, ImageTk # pip3 install pillow 13 | # 使用 ttk.Notebook 作为标签切换 14 | 15 | 16 | class CtrlMenu(object): 17 | """ 18 | 菜单栏类 19 | """ 20 | def __init__(self, father, engine, lock = None): 21 | """ 22 | CtrlMenu初始化 23 | :param father:父类窗口 24 | :param engine:引擎对象,用于推送与其他控件的请求 25 | :param lock:线程锁 26 | :return:None 27 | """ 28 | self.m_engine = engine # 负责各个组件之间数据调度的引擎 29 | self.father = father # 保存父窗口 30 | # 创建菜单栏 31 | self.menuBar = tk.Menu(father) 32 | # 将菜单栏放到主窗口 33 | self.father.config(menu=self.menuBar) 34 | # 添加菜单项 35 | self.init_modelBar(self.menuBar, ) 36 | self.init_actionBar(self.menuBar) 37 | self.init_settingBar(self.menuBar) 38 | self.init_helpBar(self.menuBar) 39 | 40 | def init_modelBar(self, menuBar): 41 | """ 42 | 初始化模型菜单子项 43 | :param menuBar: 主菜单 44 | :return: None 45 | """ 46 | self.m_model_filepath = None 47 | # 创建菜单项 48 | self.modelBar = tk.Menu(menuBar, tearoff=0) 49 | # 将菜单项添加到菜单栏 50 | menuBar.add_cascade(label=self.m_engine.word_map["Menu"]["Model"], menu=self.modelBar) 51 | # 在菜单项中加入子菜单 52 | self.modelBar.add_command(label=self.m_engine.word_map["Menu"]["Create"], command= self.click_model_create) 53 | self.modelBar.add_command(label=self.m_engine.word_map["Menu"]["Open"], command= self.click_model_open) 54 | self.modelBar.add_command(label=self.m_engine.word_map["Menu"]["Save"], command=self.click_model_save) 55 | self.modelBar.add_command(label=self.m_engine.word_map["Menu"]["SaveAs"], command= self.click_model_saveAs) 56 | # 创建分割线 57 | self.modelBar.add_separator() 58 | self.modelBar.add_command(label=self.m_engine.word_map["Menu"]["Exit"], command=self.father.destroy) 59 | 60 | def init_actionBar(self, menuBar): 61 | """ 62 | 初始化动作菜单子项 63 | :param menuBar: 主菜单 64 | :return: None 65 | """ 66 | self.m_action_filepath = None 67 | # 创建菜单项 68 | self.actionBar = tk.Menu(menuBar, tearoff=0) 69 | # 将菜单项添加到菜单栏 70 | menuBar.add_cascade(label=self.m_engine.word_map["Menu"]["Action"], menu=self.actionBar) 71 | # 在菜单项中加入子菜单 72 | self.actionBar.add_command(label=self.m_engine.word_map["Menu"]["Create"], command= self.click_action_create) 73 | self.actionBar.add_command(label=self.m_engine.word_map["Menu"]["Open"], command= self.click_action_open) 74 | self.actionBar.add_command(label=self.m_engine.word_map["Menu"]["Save"], command=self.click_action_save) 75 | self.actionBar.add_command(label=self.m_engine.word_map["Menu"]["SaveAs"], command= self.click_action_saveAs) 76 | # 创建分割线 77 | # self.actionBar.add_separator() 78 | # self.actionBar.add_command(label=self.m_engine.word_map["Menu"]["Exit"], command=self.father.destroy) 79 | 80 | def init_settingBar(self, menuBar): 81 | """ 82 | 初始化设置菜单子项 83 | :param menuBar: 主菜单 84 | :return: None 85 | """ 86 | # 创建一个tk回调参数变量 87 | vLang = tk.StringVar() 88 | def language_choose(): 89 | """ 90 | 语言设置触发函数 91 | """ 92 | global word_map 93 | print(vLang.get()) 94 | # 更新语言文本操作请求推送到引擎 95 | self.m_engine.OnThreadMessage(mh.M_CTRLMENU, mh.M_LANGUAGE, 96 | mh.A_UPDATALANG, vLang.get()) 97 | 98 | v_fileManager = tk.IntVar() 99 | v_transInfo = tk.IntVar() 100 | 101 | def view_choose(): 102 | """ 103 | 视图设置触发函数 104 | """ 105 | print(v_fileManager.get()) 106 | print(v_transInfo.get()) 107 | 108 | # 创建菜单项 109 | self.settingBar = tk.Menu(menuBar, tearoff=0) 110 | # 将菜单项添加到菜单栏 111 | menuBar.add_cascade(label=self.m_engine.word_map["Menu"]["Setting"], menu=self.settingBar) 112 | 113 | self.languageBar = tk.Menu(self.settingBar, tearoff=0) 114 | for lang in self.m_engine.v_lang: 115 | self.languageBar.add_radiobutton( label=lang, command=language_choose, variable = vLang) 116 | # languageBar.add_separator() # 分割线 117 | # 在菜单项中加入子菜单 118 | self.settingBar.add_cascade(label=self.m_engine.word_map["Menu"]['Language'], menu=self.languageBar) 119 | 120 | self.viewBar = tk.Menu(self.settingBar, tearoff=0) 121 | self.viewBar.add_checkbutton( label=self.m_engine.word_map["Menu"]['FileManager'], 122 | command=view_choose, variable = v_fileManager) 123 | self.viewBar.add_checkbutton( label=self.m_engine.word_map["Menu"]['TransInfo'], 124 | command=view_choose, variable = v_transInfo) 125 | 126 | # 在菜单项中加入子菜单 127 | self.settingBar.add_cascade(label=self.m_engine.word_map["Menu"]['View'], menu=self.viewBar) 128 | 129 | def init_helpBar(self, menuBar): 130 | """ 131 | 初始化"帮助"菜单子项 132 | :param menuBar: 主菜单 133 | :return: None 134 | """ 135 | # 创建菜单项 136 | self.helpBar = tk.Menu(menuBar, tearoff=0) 137 | # 将菜单项添加到菜单栏 138 | menuBar.add_cascade(label=self.m_engine.word_map["Menu"]["Help"], menu=self.helpBar) 139 | # 在菜单项中加入子菜单 140 | self.helpBar.add_command(label=self.m_engine.word_map["Menu"]["Registration"], 141 | command=self.click_Regist) 142 | self.helpBar.add_command(label=self.m_engine.word_map["Menu"]["About"], 143 | command=self.click_About) 144 | 145 | def click_model_create(self): 146 | """ 147 | 点击模型"创建"菜单项触发的函数 148 | :return: None 149 | """ 150 | # 打开文件对话框 获取文件路径 151 | # defaultextension 为选取保存类型中的拓展名为文件名 152 | # filetypes为文件拓展名 153 | filepath = tk.filedialog.asksaveasfilename( 154 | title='选择一个模型文件', 155 | defaultextension =".espace", 156 | filetypes = [('Model', '.mo'),('Normal', '.nor')]) 157 | if filepath == None or filepath == "": 158 | return None 159 | self.m_model_filepath = filepath 160 | self.m_engine.OnThreadMessage(mh.M_CTRLMENU, mh.M_MODEL_FILEMANAGER, 161 | mh.A_FILE_CREATE, self.m_model_filepath) 162 | 163 | def click_model_open(self): 164 | """ 165 | 点击模型"打开"菜单项触发的函数 166 | :return: 167 | """ 168 | # 打开文件对话框 获取文件路径 169 | # defaultextension 为选取保存类型中的拓展名为文件名 170 | # filetypes为文件拓展名 171 | filepath = tk.filedialog.askopenfilename( 172 | title='选择一个模型文件', 173 | defaultextension =".espace", 174 | filetypes = [('Model', '.mo'),('Normal', '.nor')]) 175 | if filepath == None or filepath == "": 176 | return None 177 | self.m_model_filepath = filepath 178 | self.m_engine.OnThreadMessage(mh.M_CTRLMENU, mh.M_MODEL_FILEMANAGER, 179 | mh.A_FILE_OPEN, self.m_model_filepath) 180 | 181 | def click_model_save(self): 182 | """ 183 | 点击模型"保存"菜单项触发的函数 184 | :return: 185 | """ 186 | if self.m_model_filepath == None: 187 | tk.messagebox.showinfo("操作有误","请选择一个模型文件") 188 | else: 189 | self.m_engine.OnThreadMessage(mh.M_CTRLMENU, mh.M_MODEL_FILEMANAGER, 190 | mh.A_FILE_SAVE, self.m_model_filepath) 191 | 192 | def click_model_saveAs(self): 193 | """ 194 | 点击模型"另存为"菜单项触发的函数 195 | :return: 196 | """ 197 | # 打开文件对话框 获取文件路径 198 | # defaultextension 为选取保存类型中的拓展名为文件名 199 | # filetypes为文件拓展名 200 | filepath = tk.filedialog.asksaveasfilename( 201 | title='', 202 | defaultextension =".espace", 203 | filetypes = [('Model', '.mo'),('Normal', '.nor')]) 204 | if filepath == None or filepath == "": 205 | return None 206 | self.m_model_filepath = filepath 207 | self.m_engine.OnThreadMessage(mh.M_CTRLMENU, mh.M_MODEL_FILEMANAGER, 208 | mh.A_FILE_SAVEAS, self.m_model_filepath) 209 | 210 | def click_action_create(self): 211 | """ 212 | 点击动作"创建"菜单项触发的函数 213 | :return: None 214 | """ 215 | # 打开文件对话框 获取文件路径 216 | # defaultextension 为选取保存类型中的拓展名为文件名 217 | # filetypes为文件拓展名 218 | filepath = tk.filedialog.asksaveasfilename( 219 | title='选择一个动作组文件', 220 | defaultextension=".espace", 221 | filetypes=[('Action', '.act'), ('Normal', '.nor')]) 222 | if filepath == None or filepath == "": 223 | return None 224 | self.m_action_filepath = filepath 225 | self.m_engine.OnThreadMessage(mh.M_CTRLMENU, mh.M_ACTION_FILEMANAGER, 226 | mh.A_FILE_CREATE, self.m_action_filepath) 227 | 228 | def click_action_open(self): 229 | """ 230 | 点击动作"打开"菜单项触发的函数 231 | :return: 232 | """ 233 | # 打开文件对话框 获取文件路径 234 | # defaultextension 为选取保存类型中的拓展名为文件名 235 | # filetypes为文件拓展名 236 | filepath = tk.filedialog.askopenfilename( 237 | title='选择一个模型文件', 238 | defaultextension=".espace", 239 | filetypes=[('Action', '.act'), ('Normal', '.nor')]) 240 | if filepath == None or filepath == "": 241 | return None 242 | self.m_action_filepath = filepath 243 | self.m_engine.OnThreadMessage(mh.M_CTRLMENU, mh.M_ACTION_FILEMANAGER, 244 | mh.A_FILE_OPEN, self.m_action_filepath) 245 | 246 | def click_action_save(self): 247 | """ 248 | 点击动作"保存"菜单项触发的函数 249 | :return: 250 | """ 251 | if self.m_action_filepath == None: 252 | tk.messagebox.showinfo("操作有误", "请选择一个模型文件") 253 | else: 254 | self.m_engine.OnThreadMessage(mh.M_CTRLMENU, mh.M_ACTION_FILEMANAGER, 255 | mh.A_FILE_SAVE, self.m_action_filepath) 256 | 257 | def click_action_saveAs(self): 258 | """ 259 | 点击动作"另存为"菜单项触发的函数 260 | :return: 261 | """ 262 | # 打开文件对话框 获取文件路径 263 | # defaultextension 为选取保存类型中的拓展名为文件名 264 | # filetypes为文件拓展名 265 | filepath = tk.filedialog.asksaveasfilename( 266 | title='', 267 | defaultextension=".espace", 268 | filetypes=[('Action', '.act'), ('Normal', '.nor')]) 269 | if filepath == None or filepath == "": 270 | return None 271 | self.m_action_filepath = filepath 272 | self.m_engine.OnThreadMessage(mh.M_CTRLMENU, mh.M_ACTION_FILEMANAGER, 273 | mh.A_FILE_SAVEAS, self.m_action_filepath) 274 | 275 | def click_About(self): 276 | """ 277 | 点击"帮助"->"关于"菜单项触发的函数 278 | :return: None 279 | """ 280 | if tk.messagebox.showinfo(self.helpBar.entrycget(1, "label"), 281 | "具体请访问 https://github.com/ClimbSnail/RobotGeneralController"): 282 | pass 283 | 284 | def click_Regist(self): 285 | """ 286 | 点击"注册"菜单项触发的函数 287 | 获取字符串(标题,提示,初始值) 288 | :return: 289 | """ 290 | result = tk.simpledialog.askstring(title=self.helpBar.entrycget(0, "label"), 291 | prompt=self.m_engine.word_map["MassageBox"]["UserCode"], 292 | initialvalue=self.m_engine.word_map["MassageBox"]["UserCodeText"]) 293 | 294 | def updata_lang(self, word_map): 295 | """ 296 | 更新文字语种接口 297 | :param word_map: 字段map: key->value 298 | :return: None 299 | """ 300 | self.father.title(word_map["Windows"]["Title"]) # 窗口名 301 | # 更新菜单栏的菜单项文字 302 | self.menuBar.entryconfigure(1, label=word_map["Menu"]["Model"]) 303 | self.menuBar.entryconfigure(2, label=word_map["Menu"]["Action"]) 304 | self.menuBar.entryconfigure(3, label=word_map["Menu"]["Setting"]) 305 | self.menuBar.entryconfigure(4, label=word_map["Menu"]["Help"]) 306 | # 更新菜单项下 模型的子菜单文字 307 | self.modelBar.entryconfigure(0, label=word_map["Menu"]["Create"]) 308 | self.modelBar.entryconfigure(1, label=word_map["Menu"]["Open"]) 309 | self.modelBar.entryconfigure(2, label=word_map["Menu"]["Save"]) 310 | self.modelBar.entryconfigure(3, label=word_map["Menu"]["SaveAs"]) 311 | self.modelBar.entryconfigure(5, label=word_map["Menu"]["Exit"]) 312 | # 更新菜单项下 动作组的子菜单文字 313 | self.actionBar.entryconfigure(0, label=word_map["Menu"]["Create"]) 314 | self.actionBar.entryconfigure(1, label=word_map["Menu"]["Open"]) 315 | self.actionBar.entryconfigure(2, label=word_map["Menu"]["Save"]) 316 | self.actionBar.entryconfigure(3, label=word_map["Menu"]["SaveAs"]) 317 | # self.actionBar.entryconfigure(5, label=word_map["Menu"]["Exit"]) 318 | # 更新菜单项下的子菜单文字 319 | self.settingBar.entryconfigure(0, label=word_map["Menu"]['Language']) 320 | self.languageBar = tk.Menu(self.settingBar, tearoff=0) 321 | for lang in self.m_engine.v_lang: 322 | self.languageBar.entryconfigure( self.m_engine.v_lang.index(lang), label=lang) 323 | self.settingBar.entryconfigure(1, label=word_map["Menu"]['View']) 324 | self.viewBar.entryconfigure( 0, label=word_map["Menu"]['FileManager']) 325 | self.viewBar.entryconfigure( 1, label=word_map["Menu"]['TransInfo']) 326 | # 帮助 327 | self.helpBar.entryconfigure( 0, label=word_map["Menu"]['Registration']) 328 | self.helpBar.entryconfigure( 1, label=word_map["Menu"]['About']) 329 | 330 | def api(self, action, param=None): 331 | """ 332 | 外界操作舵机控件管理器的api接口 333 | """ 334 | if action == mh.A_UPDATALANG: # 更新语言 335 | self.updata_lang(self.m_engine.word_map) 336 | 337 | class ServoMotorModel(object): 338 | """ 339 | 舵机控件类 340 | """ 341 | def __init__(self, father, start_x = 0, start_y = 0, name = "S0", model_en=True, manager = None): 342 | """ 343 | 舵机控件类初始化 344 | :param father: 父类空间 345 | :param start_x: 本舵机控件起始坐标 346 | :param start_y: 本舵机控件起始坐标 347 | :param name: 本舵机控件名称 348 | :param manager: 引擎 349 | """ 350 | self.m_father = father 351 | self.m_manager = manager 352 | self.m_name = name 353 | self.my_ft1 = tkFont.Font(family="微软雅黑", size=7, weight=tkFont.BOLD) # 定义字体 354 | self.my_ft2 = tkFont.Font(family="微软雅黑", size=10, weight=tkFont.BOLD) # 定义字体 355 | self.m_model_en = model_en # 舵机的使能标志位 356 | self.m_val_min = 500 # 控件控制的数值最小值 357 | self.m_val_max = 2500 # 控件控制的数值最大值 358 | self.m_frame = tk.Frame(self.m_father, bg="LightYellow",highlightthickness = 1) 359 | self.create_element(self.m_frame, self.m_name) 360 | self.m_start_x = start_x # 本控件相对于父容器的起点x坐标 361 | self.m_start_y = start_y # 本控件相对于父容器的起点y坐标 362 | self.m_frame.place(x = self.m_start_x, y = self.m_start_y) 363 | self.m_frame.update() # 一定要更新 才可得到winfo_width winfo_height真实值 364 | self.m_centerX = self.m_frame.winfo_width()/2 365 | self.m_centerY = self.m_frame.winfo_height()/2 366 | self.m_frame.bind("", self.mouseMove) # 鼠标拖拽 367 | self.m_frame.bind("", self.mouseDoubleClick) # 鼠标左键双击 368 | self.m_frame.bind("", self.mouseSingleClick) # 鼠标左键单击 369 | 370 | def mouseMove(self, event): 371 | """ 372 | 鼠标移动事件 373 | :param event: 事件 374 | :return: None 375 | """ 376 | # 检测模型是否可编辑 377 | if self.m_manager.m_model_editable == False: 378 | return None 379 | self.m_start_x = self.m_start_x +( event.x-self.m_centerX) 380 | self.m_start_y = self.m_start_y +( event.y-self.m_centerY) 381 | # 添加移动的边框横向限制条件 382 | if self.m_start_x > self.m_father.winfo_width()-self.m_frame.winfo_width(): 383 | self.m_start_x = self.m_father.winfo_width()-self.m_frame.winfo_width() 384 | if self.m_start_x < 0: 385 | self.m_start_x = 0 386 | # 添加移动的边框纵向限制条件 387 | if self.m_start_y > self.m_father.winfo_height()-self.m_frame.winfo_height(): 388 | self.m_start_y = self.m_father.winfo_height()-self.m_frame.winfo_height() 389 | if self.m_start_y < 0: 390 | self.m_start_y = 0 391 | # 重新设置窗口的位置 392 | self.m_frame.place(x=self.m_start_x, y=self.m_start_y) 393 | 394 | def mouseDoubleClick(self, event): 395 | """ 396 | 鼠标左键双击事件 397 | :param event: 事件 398 | :return: None 399 | """ 400 | # 检测模型是否可编辑 401 | if self.m_manager.m_model_editable == False: 402 | return None 403 | self.m_manager.api(mh.A_SELECT, self.m_name) 404 | 405 | def mouseSingleClick(self, event): 406 | """ 407 | 鼠标单击触发本函数 408 | :param event: 事件 409 | :return: 410 | """ 411 | self.m_manager.api(mh.A_SELECT, None) 412 | 413 | def create_element(self, father, name): 414 | """ 415 | 创建一个舵机控件 416 | :param father:父类窗口 417 | :param name: 本舵机控件的名字 418 | :return: None 419 | """ 420 | self.m_name_label = tk.Label(father, text=name, font=self.my_ft2, bg=father['bg']) 421 | # 创建输入框 422 | self.m_val_entry = tk.Entry(father, font=self.my_ft1, width=5, highlightcolor="LightGrey") 423 | self.m_val_entry.insert(tk.END, '1500') 424 | self.m_pre_val_text = "1500" # 保存修改前m_val_text输入框中的内容,供错误输入时使用 425 | self.m_val_entry.bind("", self.change_val) # 绑定enter键的触发 426 | # 创建范围控件 427 | self.m_val = tk.StringVar() 428 | self.m_scale = tk.Scale(father, 429 | from_=self.m_val_min, # 设置最小值 430 | to=self.m_val_max, # 设置最大值 431 | resolution=2, # 设置步距值 432 | bd = 1, # 槽的边框 433 | length=100, # 200像素 434 | width = 10, # 字符宽度 435 | sliderlength = 8, # 滑块长度 436 | showvalue=0, # 是否显示当前值 437 | orient=tk.HORIZONTAL, # 设置水平方向 438 | variable=self.m_val, # 绑定变量 439 | command = self.set_entry_val, # 设置回调函数 440 | bg = father['bg'], # 背景 441 | troughcolor = "white", # 槽的颜色 442 | # highlightbackground = "black", 443 | cursor = "arrow", # 光标样式 444 | takefocus = 0, 445 | relief = tk.FLAT, 446 | ) 447 | # 范围控件预设值 448 | self.m_scale.set( (self.m_val_min+self.m_val_max)/2 ) 449 | self.m_scale.pack(side=tk.BOTTOM) 450 | self.m_name_label.pack(side=tk.LEFT, padx=10, pady=2) 451 | self.m_val_entry.pack(side=tk.RIGHT, padx=10) # 将小部件放置到窗口中 452 | 453 | def set_entry_val(self, val): 454 | """ 455 | 设置文本输入框中的值 456 | :param val: 设置的目标值 457 | :return: None 458 | """ 459 | self.m_val_entry.delete(0, tk.END) # 清空文本框 460 | self.m_val_entry.insert(tk.END, val) # 插入新值 461 | self.m_pre_val_text = val 462 | 463 | def change_val(self, extern_val=None): 464 | """ 465 | 文本输入框按下Enter将触发本函数 466 | :param extern_val: 设置的目标值 467 | :return: None 468 | """ 469 | m_val_entry = None 470 | if extern_val == None or type(extern_val) == tk.Event: # 当控件的事件被触发extern_val为一个控件对象 471 | m_val_entry = self.m_val_entry.get().strip() # 得到输入的文本 472 | else: 473 | m_val_entry = extern_val.strip() # 得到外部传进来的文本数值 474 | try: 475 | val = int(m_val_entry) # 得到数值 476 | val = val if val>=self.m_val_min and val<=self.m_val_max else int(self.m_pre_val_text) 477 | self.m_scale.set(val) # 数据符合范围,即可设置范围控件(进度条) 478 | except Exception as err: # 有效过滤输入的非法字符 479 | print(err) 480 | 481 | self.set_entry_val(self.m_pre_val_text) # 输入结束,将文本框更新为目标值 482 | 483 | def __del__(self): 484 | # self.m_frame.pack_forget() 485 | # Release resources 486 | self.m_frame.destroy() 487 | 488 | class ModelManager(object): 489 | """ 490 | ServoMotorModel(舵机控件)管理器 491 | """ 492 | def __init__(self, father, engine = None, width=700, height=500): 493 | """ 494 | ServoMotorModel(舵机控件)管理器 初始化 495 | :param father:父类控件 496 | :param engine:引擎对象,用于推送与其他控件的请求 497 | :param width:本控件的宽度 498 | :param height:本控件的高度 499 | """ 500 | self.m_engine = engine # 保存引擎 501 | self.m_image_path = self.m_engine.m_sys_info["system_init"]["imagepath"] 502 | self.m_father = father 503 | self.width = width 504 | self.height = height 505 | self.m_frame = tk.Frame(father, width=self.width, height=self.height, bg="white") 506 | self.m_model_map = {} # 储存舵机控件 name(key):object 507 | self.m_frame.pack(side = tk.LEFT) 508 | self.m_frame.update() 509 | self.m_select_name = None 510 | self.m_model_editable = True # 标志模型是否可编辑(位置、删除) 511 | 512 | # 初始化操作按钮 513 | self.add_button = tk.Button(self.m_frame, text=self.m_engine.word_map["Action"]["Add"], 514 | command = self.add_ServoMotorModel, width=10, height=1) 515 | # button['width'] = 30 516 | # button['height'] = 20 517 | width = 80 518 | height = 30 519 | self.add_button.place(x=self.m_frame.winfo_width()-width, 520 | y=self.m_frame.winfo_height()-height*2) 521 | self.del_button = tk.Button(self.m_frame, text=self.m_engine.word_map["Action"]["Del"], 522 | command = self.del_ServoMotorModel, width=10, height=1) 523 | self.del_button.place(x=self.m_frame.winfo_width()-width, 524 | y=self.m_frame.winfo_height()-height) 525 | # 编辑图标按钮 526 | pencil_bg_image = Image.open(self.m_image_path+"pencil_16x16.ico") 527 | self.pencil_edit_img = ImageTk.PhotoImage(pencil_bg_image) 528 | pencil_bg_image = Image.open(self.m_image_path+"yes_16x16.ico") 529 | self.pencil_yes_img = ImageTk.PhotoImage(pencil_bg_image) 530 | self.pencil_button = tk.Button(self.m_frame, command = self.pencil_click, 531 | image=self.pencil_yes_img) 532 | self.pencil_button.place(x=self.m_frame.winfo_width()-30, 533 | y=self.m_frame.winfo_height()-height*3) 534 | # 用来显示模型名称 535 | self.name_label = tk.Label(self.m_frame, text="", bg=self.m_frame['bg']) # , font=self.my_ft1 536 | self.name_label.place(x=5, y=self.height-25) 537 | 538 | def add_ServoMotorModel(self): 539 | """ 540 | 添加一个ServoMotorModel(舵机控件) 541 | """ 542 | find_num = 0 # 用来产生舵机控件的编号 543 | for find_num in range(1000): 544 | if "S"+str(find_num) not in self.m_model_map.keys(): 545 | break 546 | name = "S"+str(find_num) # "S"+编号组合成ServoMotorModel(舵机控件)的名字 当作m_model_map的key值 547 | self.m_model_map[name] = ServoMotorModel( 548 | self.m_frame, start_x = 50, start_y = 50, name = name, model_en=True, manager = self) 549 | 550 | def del_ServoMotorModel(self): 551 | if self.m_select_name != None: 552 | # 先释放控件对象 553 | self.m_model_map[self.m_select_name].__del__() 554 | # 删除字典 555 | del self.m_model_map[self.m_select_name] 556 | self.m_select_name = None 557 | 558 | def pencil_click(self): 559 | """ 560 | 修改按钮按下之后触发编辑状态改变 561 | :return: 562 | """ 563 | if self.m_model_editable == False: 564 | self.set_model_edit_state(True) 565 | else: 566 | self.set_model_edit_state(False) 567 | 568 | def set_model_edit_state(self, status): 569 | """ 570 | 设置模型是否处于可编辑状态 571 | :return: None 572 | """ 573 | if status == False: 574 | # 标志模型是否可编辑(位置、删除) 575 | self.m_model_editable = False 576 | # 编辑图标 577 | self.pencil_button["image"] = self.pencil_edit_img 578 | # 设置按键不可按 579 | self.add_button["state"] = tk.DISABLED 580 | self.del_button["state"] = tk.DISABLED 581 | else: 582 | # 标志模型是否可编辑(位置、删除) 583 | self.m_model_editable = True 584 | # 编辑图标 585 | self.pencil_button["image"] = self.pencil_yes_img 586 | # 设置按键不可按 587 | self.add_button["state"] = tk.NORMAL 588 | self.del_button["state"] = tk.NORMAL 589 | 590 | def updata_lang(self, val_map): 591 | """ 592 | 更新字段语言 593 | :param val_map: 字段所对应的map 594 | :return: 595 | """ 596 | self.add_button["text"] = val_map["Add"] 597 | self.del_button["text"] = val_map["Del"] 598 | 599 | def get_all_motor_model_info(self): 600 | """ 601 | 获取模型控件所在的坐标位置, 使能状态, 控件上的值(值暂时用不上) 602 | :return: 所有舵机的列表集合 603 | """ 604 | res = {} 605 | for name, model in self.m_model_map.items(): 606 | res[name] = {"pos_x":model.m_start_x, "pos_y":model.m_start_y, 607 | "model_en":model.m_model_en, "val":model.m_pre_val_text} 608 | return res 609 | 610 | def load_all_model(self, model_info): 611 | """ 612 | 通过传入的模型列表信息 生成对应的模型 613 | :param model_info: 614 | :return: 615 | """ 616 | # 先清空原先的模型 617 | keyList = list(self.m_model_map.keys()) 618 | for key in keyList: 619 | # 先释放控件对象 620 | self.m_model_map[key].__del__() 621 | # 删除字典 622 | del self.m_model_map[key] 623 | self.m_select_name = None 624 | # 设置模型处于不可编辑状态 625 | self.set_model_edit_state(False) 626 | # 添加指定的舵机模型控件 627 | for name, info in model_info.items(): 628 | self.m_model_map[name] = ServoMotorModel( 629 | self.m_frame, start_x=info["pos_x"], start_y=info["pos_y"], name=name, model_en=info["model_en"], manager=self) 630 | return True 631 | 632 | def set_model_name(self, model_name): 633 | """ 634 | 设置模型标识的名字 635 | :param model_name: 模型的目标名字 636 | :return: None 637 | """ 638 | self.name_label["text"] = model_name 639 | 640 | def updata_all_model_val(self, param): 641 | #change_val 642 | pass 643 | 644 | def api(self, action, param = None): 645 | """ 646 | 外界操作舵机控件管理器的api接口 647 | :param action: 动作类型 648 | :param param: 携带参数 649 | :return: None 650 | """ 651 | if action == mh.A_CREATE_MODEL: # 创建新控件(舵机控件) 652 | self.add_ServoMotorModel() 653 | elif action == mh.A_DEL_MODEL: # 删除(被选择的)控件(舵机控件) 654 | self.del_ServoMotorModel() 655 | elif action == mh.A_SELECT: # 选择控件(舵机控件),之后才可进行删除操作 656 | self.m_select_name = param 657 | elif action == mh.A_UPDATALANG: # 更新语言 658 | self.updata_lang(self.m_engine.word_map["Action"]) 659 | elif action == mh.A_GET_MODELINFO: 660 | return self.get_all_motor_model_info() 661 | elif action == mh.A_SET_MODELINFO: 662 | self.load_all_model(param) 663 | elif action == mh.A_SET_MODELNAME: 664 | self.set_model_name(param) 665 | elif action == mh.A_UPDATA_ALL_MODEL_VAL: 666 | self.updata_all_model_val(param) 667 | return None 668 | 669 | def __del__(self): 670 | """ 671 | 主要工作:释放舵机控件所占用的资源 672 | :return: None 673 | """ 674 | # 释放模型控件资源 675 | keyList = list(self.m_model_map.keys()) 676 | for key in keyList: 677 | # 先释放控件对象 678 | self.m_model_map[key].__del__() 679 | # 删除字典 680 | del self.m_model_map[key] 681 | self.m_select_name = None 682 | 683 | class Connector(object): 684 | """ 685 | 连接器类:管理各个连接相关的按钮 686 | """ 687 | def __init__(self, father, engine = None): 688 | """ 689 | ServoMotorModel(舵机控件)管理器 初始化 690 | :param father:父类控件 691 | :param engine:引擎对象,用于推送与其他控件的请求 692 | """ 693 | self.m_father = father 694 | self.m_engine = engine 695 | # 单选按钮 696 | self.m_radio_val = tk.IntVar() # IntVar 697 | radio_frame = tk.Frame(self.m_father, bg="DimGray") 698 | tk.Radiobutton(radio_frame, variable=self.m_radio_val, value=0, 699 | text="COM", width=5, bg="DimGray", 700 | command=self.radio_select).pack(side=tk.LEFT, pady=5) 701 | 702 | tk.Radiobutton(radio_frame, variable=self.m_radio_val, value=1, 703 | text="TCP", width=5, bg="DimGray", 704 | command=self.radio_select).pack(side=tk.LEFT) 705 | self.m_radio_val.set(0) 706 | radio_frame.pack(side=tk.TOP, padx=5, fill="x") 707 | 708 | # 连接的信息框 根据单选按钮来选择显示的内容 709 | self.link_param_frame = tk.Frame(self.m_father, bg=self.m_father["bg"]) 710 | self.link_param_frame.pack(side=tk.TOP, pady=5) 711 | # 串口窗口 712 | self.uart_param_frame = tk.Frame(self.link_param_frame, bg=self.m_father["bg"]) 713 | self.uart_param_frame.pack(side=tk.TOP, pady=5) 714 | self.create_com(self.uart_param_frame) 715 | # self.link_param_frame.pack_forget() 716 | # TCP窗口 717 | self.tcp_param_frame = tk.Frame(self.link_param_frame, bg=self.m_father["bg"]) 718 | self.tcp_param_frame.pack(side=tk.TOP, pady=5) 719 | self.create_tcp(self.tcp_param_frame) 720 | # 先将其隐藏 721 | self.tcp_param_frame.pack_forget() 722 | 723 | # 连接按钮 724 | self.m_connect = tk.Button(self.m_father, text=self.m_engine.word_map["Connect"]["Connect"], 725 | command = self.connect, width=15, height=1) 726 | self.m_connect.pack(side = tk.TOP, fill=tk.X, padx=5) 727 | 728 | def create_com(self, father): 729 | """ 730 | 创建Comm相关控件 731 | :param father: 父类窗口 732 | :return: None 733 | """ 734 | # 窗口 735 | com_frame = tk.Frame(father, bg=self.m_father["bg"]) 736 | self.m_com_label = tk.Label(com_frame, text=self.m_engine.word_map["Connect"]["UartNo"], 737 | #font=self.my_ft1, 738 | bg=self.m_father['bg']) 739 | self.m_com_label.pack(side=tk.LEFT, padx=10) 740 | self.com_combo = ttk.Combobox(com_frame, width=8) 741 | self.com_combo["value"] = ('COM0','COM1') 742 | # 设置默认值,即默认下拉框中的内容 743 | self.com_combo.current(0) 744 | self.com_combo.pack(side=tk.RIGHT, padx=10) 745 | com_frame.pack(side=tk.TOP, pady=5) 746 | 747 | baud_frame = tk.Frame(father, bg=self.m_father["bg"]) 748 | self.m_baud_label = tk.Label(baud_frame, text=self.m_engine.word_map["Connect"]["BaudRate"], 749 | #font=self.my_ft1, 750 | bg=self.m_father['bg']) 751 | self.m_baud_label.pack(side=tk.LEFT, padx=10) 752 | self.m_baud_select = ttk.Combobox(baud_frame, width=8) 753 | self.m_baud_select["value"] = ('9600','115200') 754 | # 设置默认值,即默认下拉框中的内容 755 | self.m_baud_select.current(0) 756 | self.m_baud_select.pack(side=tk.RIGHT, padx=10) 757 | baud_frame.pack(side=tk.TOP, pady=5) 758 | 759 | def create_tcp(self, father): 760 | """ 761 | 创建TCP相关控件 762 | :param father: 父类窗口 763 | :return: None 764 | """ 765 | # 窗口 766 | ip_frame = tk.Frame(father, bg=self.m_father["bg"]) 767 | self.m_ip_label = tk.Label(ip_frame, text=self.m_engine.word_map["Connect"]["IpAddr"], 768 | #font=self.my_ft1, 769 | bg=self.m_father['bg']) 770 | self.m_ip_label.pack(side=tk.LEFT, padx=5) 771 | self.m_ip_entry = tk.Entry(ip_frame, 772 | # font=self.my_ft1, 773 | width=15, highlightcolor="LightGrey") 774 | self.m_ip_entry.insert(tk.END, '192.168.1.1') 775 | self.m_ip_entry.pack(side=tk.RIGHT, padx=5) 776 | ip_frame.pack(side=tk.TOP, pady=5) 777 | 778 | port_frame = tk.Frame(father, bg=self.m_father["bg"]) 779 | self.m_port_label = tk.Label(port_frame, text=self.m_engine.word_map["Connect"]["Port"], 780 | #font=self.my_ft1, 781 | bg=self.m_father['bg']) 782 | self.m_port_label.pack(side=tk.LEFT, padx=5) 783 | self.m_port_entry = tk.Entry(port_frame, 784 | # font=self.my_ft1, 785 | width=5, highlightcolor="LightGrey") 786 | self.m_port_entry.insert(tk.END, '8080') 787 | self.m_port_entry.pack(side=tk.RIGHT, padx=5) 788 | port_frame.pack(side=tk.TOP, pady=5) 789 | 790 | def radio_select(self): 791 | """ 792 | 选择触发的函数 793 | :return: 794 | """ 795 | if self.m_radio_val.get() == 0: 796 | self.tcp_param_frame.pack_forget() 797 | self.uart_param_frame.pack() 798 | elif self.m_radio_val.get() == 1: 799 | self.uart_param_frame.pack_forget() 800 | self.tcp_param_frame.pack() 801 | print("Radio select: ", self.m_radio_val.get()) 802 | 803 | def connect(self): 804 | """ 805 | 创建连接按钮 806 | :return: 807 | """ 808 | if self.m_connect["text"] == self.m_engine.word_map["Connect"]["Connect"]: 809 | # 连接请求推送到引擎 810 | self.m_engine.OnThreadMessage(mh.M_CONNECTOR, mh.M_SOCKET_MAIN, mh.A_CONNECT) 811 | self.m_connect["text"] = self.m_engine.word_map["Connect"]["DisConnect"] 812 | else: 813 | self.m_connect["text"] = self.m_engine.word_map["Connect"]["Connect"] 814 | # 断开请求推送到引擎 815 | self.m_engine.OnThreadMessage(mh.M_CONNECTOR, mh.M_SOCKET_MAIN, mh.A_DISCONNECT) 816 | 817 | def api(self, action, param = None): 818 | """ 819 | 连接器类向外提供的操作api 820 | :param action: 动作类型 821 | :param param: 携带参数 822 | :return: None 823 | """ 824 | pass 825 | 826 | def updata_lang(self): 827 | """ 828 | 更新字段语言 829 | :return: None 830 | """ 831 | pass 832 | 833 | def __del__(self): 834 | pass 835 | 836 | class InfoOut(object): 837 | """ 838 | 日志打印类 839 | """ 840 | def __init__(self, father, engine = None): 841 | """ 842 | 日志打印类初始化 843 | :param father: 父类空间 844 | :param engine: 引擎 845 | """ 846 | self.m_father = father 847 | self.m_engine = engine 848 | self.m_father.update() 849 | self.info_width = self.m_father.winfo_width() 850 | self.info_height = self.m_father.winfo_height()/2 851 | # 滑动条 852 | self.m_scrollbar = tk.Scrollbar(self.m_father, width=12) 853 | # 信息文本框 854 | self.m_msg = tk.Text(self.m_father, width = self.info_width, height = self.info_height, yscrollcommand=self.m_scrollbar.set, state=tk.DISABLED) 855 | # 两个控件关联 856 | self.m_scrollbar.config(command=self.m_msg.yview) 857 | self.m_msg.pack(side=tk.LEFT, fill=tk.Y, padx=3, pady=3) 858 | 859 | # 清空按键 860 | self.m_clear = tk.Button(self.m_father, text="X", command = self.msg_clear, width=1, height=1, font=('Helvetica', '4')) 861 | self.m_clear.pack(side=tk.BOTTOM, fill=tk.X, pady=1) 862 | 863 | self.m_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) 864 | 865 | def updata_lang(self, val_map): 866 | # self.m_info_out["text"] = val_map["title"] 867 | pass 868 | 869 | def print(self, info): 870 | """ 871 | 消息打印(打印到消息文本框中) 872 | :param info: 要打印的内容 873 | :return: 874 | """ 875 | if info == None or info == "": 876 | return False 877 | self.m_msg.config(state=tk.NORMAL) 878 | self.m_msg.insert(tk.END, info) 879 | self.m_msg.config(state=tk.DISABLED) 880 | return True 881 | 882 | def msg_clear(self): 883 | """ 884 | 清空日志按钮 885 | :return: None 886 | """ 887 | self.m_msg.config(state=tk.NORMAL) 888 | self.m_msg.delete(1.0, tk.END) 889 | self.m_msg.config(state=tk.DISABLED) 890 | 891 | def api(self, action, param = None): 892 | if action == mh.A_INFO_PRINT: 893 | self.print(param) 894 | elif action == mh.A_UPDATALANG: 895 | self.updata_lang(self.m_engine.word_map["SysOut"]) 896 | 897 | def __del__(self): 898 | pass 899 | 900 | class GroupActionTable(object): 901 | """ 902 | 动作组管理器 903 | """ 904 | def __init__(self, father, engine = None, width=700, height=200): 905 | """ 906 | 栋动作组管理器 初始化 907 | :param father:父类控件 908 | :param engine:引擎对象,用于推送与其他控件的请求 909 | """ 910 | self.m_father = father 911 | self.m_engine = engine 912 | self.m_image_path = self.m_engine.m_sys_info["system_init"]["imagepath"] 913 | self.m_width = width 914 | self.m_height = height 915 | self.m_tree_head_l = ["编号", "状态", "运行", "移动"] # 表头字段 916 | self.m_tree_head_r = [] 917 | self.m_tree_width = [40, 40, 40, 40] # 列对应的宽度 918 | self.ui_init() 919 | self.m_single_select_row = None # 标记当前动作组表格单击选中的行 920 | self.m_double_select_row = None # 标记当前动作组表格双击选中的行 921 | 922 | 923 | 924 | def ui_init(self): 925 | """ 926 | 创建动作组表格 927 | :return: None 928 | """ 929 | self.m_frame = tk.Frame(self.m_father, bg = "LightGrey") 930 | self.m_frame.pack(padx=5, pady=5) 931 | # 滑动条 932 | self.m_scrollbar_y = tk.Scrollbar(self.m_frame) 933 | self.m_scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y) 934 | self.m_scrollbar_x = tk.Scrollbar(self.m_frame) 935 | self.m_scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X) 936 | self.init_button_list(self.m_frame) 937 | # 两个控件关联 938 | row_num = 5 # 设置表格显示多少行 939 | self.m_tree = ttk.Treeview(self.m_frame, show = "headings", height = row_num, 940 | xscrollcommand=self.m_scrollbar_x.set, 941 | yscrollcommand=self.m_scrollbar_y.set, 942 | selectmode = tk.BROWSE, 943 | #selectmode = tk.EXTENDED 944 | ) 945 | 946 | # height为显示多少行数据 headings为只显示表头 947 | self.m_scrollbar_y.config(command=self.m_tree.yview, orient=tk.VERTICAL) 948 | self.m_scrollbar_x.config(command=self.m_tree.xview, orient=tk.HORIZONTAL) 949 | self.m_scrollbar_y.update() 950 | self.m_scrollbar_x.update() 951 | 952 | # 以下代码设置表格的行高 953 | rowheight = (self.m_height-self.m_scrollbar_x.winfo_height())//row_num 954 | style = ttk.Style() 955 | style.configure('Treeview', rowheight=rowheight) # repace 40 with whatever you need 956 | self.updata_table_head(self.m_tree_head_l+self.m_tree_head_r, self.m_tree_width.copy()) 957 | 958 | self.m_tree.pack(side=tk.LEFT, expand = False) # 设置表格最大化 959 | # 鼠标事件注册 960 | self.m_tree.bind('', self.double_click_row) # 左键双击 961 | self.m_tree.bind('', self.single_click_row) # 左键单击 962 | # self.m_tree.bind('<>', selectTree) 963 | 964 | def init_button_list(self, father): 965 | # 表格的运行、增、删操作按钮 966 | self.m_button_frame = tk.Frame(father) 967 | 968 | # 运行按钮 969 | self.run_table_img = ImageTk.PhotoImage( Image.open(self.m_image_path+"running_16x16.ico") ) 970 | # self.run_all_im = tk.PhotoImage(file=self.m_image_path+"running.ico") 971 | self.m_run_table_button = tk.Button(self.m_button_frame, text="X", image=self.run_table_img, 972 | command = self.run_all_action ) 973 | self.m_run_table_button.pack(side=tk.TOP, fill=tk.X, pady=1) 974 | 975 | # 添加加 976 | self.add_table_img = ImageTk.PhotoImage( Image.open(self.m_image_path+"+_16x16.ico") ) 977 | self.m_add_table_button = tk.Button(self.m_button_frame, text="X", image=self.add_table_img, 978 | command = self.add_table ) 979 | self.m_add_table_button.pack(side=tk.TOP, fill=tk.X, pady=1) 980 | 981 | # 添加减 (初始化因为没选中行 所以默认按钮关闭使能) 982 | self.del_table_img = ImageTk.PhotoImage( Image.open(self.m_image_path+"-_16x16.ico") ) 983 | self.m_del_table_button = tk.Button(self.m_button_frame, text="X", image=self.del_table_img, 984 | command = self.del_table, state=tk.DISABLED ) 985 | self.m_del_table_button.pack(side=tk.TOP, fill=tk.X, pady=1) 986 | 987 | # 添加设置 988 | self.set_table_img = ImageTk.PhotoImage( Image.open(self.m_image_path+"setting_16x16.ico") ) 989 | self.m_set_table_button = tk.Button(self.m_button_frame, text="X", image=self.set_table_img, 990 | command = self.set_table ) 991 | self.m_set_table_button.pack(side=tk.TOP, fill=tk.X, pady=1) 992 | 993 | self.m_button_frame.pack(side=tk.RIGHT, fill=tk.Y) 994 | 995 | def run_all_action(self): 996 | """ 997 | 将要执行的内容数据交给引擎,调度引擎将数据发送出去 998 | :return: 999 | """ 1000 | # 遍历Treeview中所有的行 1001 | for item in self.m_tree.get_children(): 1002 | pass 1003 | #self.m_tree.set(item, column=self.task_status_ind, value="已关闭") 1004 | 1005 | def add_table(self): 1006 | if self.m_double_select_row != None: 1007 | self.m_tree.insert('', int(self.m_double_select_row[1:])-1, values=[500]) 1008 | else: 1009 | self.updata_select_row() 1010 | 1011 | def del_table(self): 1012 | """ 1013 | 删除双击选中的行 1014 | :return: None 1015 | """ 1016 | if None != self.m_double_select_row: 1017 | self.m_tree.delete(self.m_double_select_row) # 删除双击选中的行 1018 | self.m_double_select_row = None 1019 | self.m_del_table_button["state"] = tk.DISABLED # 设置删除按钮不可按 1020 | 1021 | def set_table(self): 1022 | pass 1023 | 1024 | def run_one_action(self): 1025 | """ 1026 | 将要执行的内容数据交给引擎,让其调度将数据发送出去 1027 | :return: 1028 | """ 1029 | pass 1030 | 1031 | def single_click_row(self, event): 1032 | """ 1033 | 单选表格行的时候 1034 | :param event: 单击事件 1035 | :return: None 1036 | """ 1037 | x, y, widget = event.x, event.y, event.widget 1038 | item = widget.item(widget.focus()) 1039 | itemText = item['text'] 1040 | itemValues = item['values'] 1041 | iid = widget.identify_row(y) 1042 | column = event.widget.identify_column(x) 1043 | print ('\n&&&&&&&& def selectItem(self, event):') 1044 | print ('item = ', item) 1045 | print('itemValues = ',itemValues) 1046 | 1047 | for item in self.m_tree.selection(): 1048 | item_text = self.m_tree.item(item, "values") 1049 | print(item_text) 1050 | # if "已关闭" in self.m_tree.item(item, "values")[self.task_status_ind]: 1051 | # self.m_tree.set(item, column=self.task_status_ind, value="运行中") 1052 | 1053 | # 将要更新进各个模型控件的数值交给引擎 1054 | self.m_engine.OnThreadMessage(mh.M_TREETABLE, mh.M_SMMODEL_MANAGER, mh.A_UPDATA_ALL_MODEL_VAL, None) 1055 | 1056 | self.m_single_select_row = None 1057 | self.m_double_select_row = None 1058 | 1059 | def double_click_row(self, event): 1060 | """ 1061 | 双击行之后调用本函数 1062 | :return: None 1063 | """ 1064 | select_row = self.m_tree.selection() 1065 | self.m_double_select_row = select_row[0] if len(select_row) != 0 else None 1066 | self.m_del_table_button["state"] = tk.NORMAL # 设置删除按钮不可按 1067 | self.m_single_select_row = None 1068 | 1069 | def updata_select_row(self, param=None): 1070 | """ 1071 | 将param携带的数据一一对应更新到当前选中的行 1072 | :return: 执行的结果 1073 | """ 1074 | param = ["1500", "1600", "1700", "1800", "1900", "2000", "2100", "2200"] 1075 | param.insert(0, len(self.m_tree.get_children())+1) 1076 | field_info = param 1077 | taskkey = param[0] 1078 | self.m_tree.insert("", tk.END, text=taskkey, values=field_info) # 给第末行添加数据,索引值可重复 1079 | 1080 | def updata_table_head(self, head_list, width_list): 1081 | """ 1082 | 设置表头属性名称 1083 | :param head_list: 表头名称 1084 | :param width_list: 表头名称对应列的宽度 1085 | :return: None 1086 | """ 1087 | remainder = 750-sum( width_list ) 1088 | if remainder>0: # 宽度不足时 填充 1089 | width_list.append(remainder) 1090 | head_list.append("None") 1091 | self.m_tree["columns"] = tuple(head_list) 1092 | for col, width in zip(head_list, width_list): 1093 | print(col, width, end=" ") 1094 | self.m_tree.column(col, width=width, anchor=tk.CENTER, stretch=tk.NO) # 设置列 1095 | self.m_tree.heading(col, text=col) # 设置显示的表头名 1096 | 1097 | self.m_tree.update() 1098 | print(self.m_tree.winfo_width()) 1099 | print() 1100 | 1101 | def api(self, action, param = None): 1102 | # 显示从模型管理器那同步过来的数据 1103 | if action == mh.A_UPDATA_TREE_SELECT_ROW: 1104 | self.updata_select_row(param) 1105 | elif action == mh.A_SET_TABLEHEAD: 1106 | print(param) 1107 | num = len(param.items()) 1108 | new_tree_width = self.m_tree_width+[50 for cnt in range(num)] 1109 | # 以下记得浅拷贝 1110 | head_list = self.m_tree_head_l.copy() 1111 | for name, model in param.items(): 1112 | head_list.append(name) 1113 | self.updata_table_head(head_list+self.m_tree_head_r, new_tree_width) 1114 | print() 1115 | 1116 | def __del__(self): 1117 | pass 1118 | -------------------------------------------------------------------------------- /src/configurator.py: -------------------------------------------------------------------------------- 1 | import json 2 | import codecs 3 | import massagehead as mh 4 | 5 | class Language(object): 6 | def __init__(self, path): 7 | """ 8 | 多语言管理类初始化 9 | :param path: 语言文件所在目录 10 | """ 11 | self.language_path = path 12 | self.language_setting = None # 默认语言设置 13 | self.language_key = None # 保存各个语种信息 14 | self.word_set = None # 程序所涉及的所有词组信息 15 | self.language = None # self.language_setting对应的所有词组文字 16 | self.read_langueage(self.language_path) 17 | 18 | def read_langueage(self, read_path): 19 | """ 20 | 读取语言信息 21 | :param read_path: 语言文件所在的路径 22 | :return: None 23 | """ 24 | fp = codecs.open(read_path, "r", "utf8") 25 | self.set_data = json.load(fp) 26 | fp.close() 27 | 28 | self.language_setting = self.set_data["language_set"].strip() 29 | self.language_key = self.set_data["language_key"] 30 | self.word_set = self.set_data["word_set"] 31 | self.language = self.set_data["language"] 32 | # 获取支持的语种信息,存放在list中 33 | self.language_key_list = [ key for key in self.language_key.split(", ") ] 34 | 35 | def get_data(self, language = None): 36 | """ 37 | 获取设定语种所对应的所有词组信息 38 | :param language: 语种类别 39 | :return: 所有语种信息 词组对应关系map 40 | """ 41 | language = self.language_setting if language == None else language 42 | if language in self.language_key_list: 43 | # 保存到语言文件中 44 | self.language_setting = language 45 | self.save_language(self.language_path) 46 | # 获取对应语言的文字 47 | word_keys = self.word_set.keys() 48 | word_map_map = {} 49 | for word_key in word_keys: 50 | word_key_map = self.word_set[word_key].split(", ") 51 | word_val_map = self.language[language][word_key].split(", ") 52 | word_map_map[word_key] = { key:val for key, val in zip(word_key_map, word_val_map) } 53 | return self.language_key_list, word_map_map 54 | else: 55 | print("error") 56 | return None, None 57 | 58 | def save_language(self, save_path): 59 | """ 60 | 保存配置,并写入文件 61 | :param save_path:写入目标文件的路径 62 | :return:None 63 | """ 64 | fp = codecs.open(save_path, "w", "utf8") 65 | w_data = { 66 | "language_set": self.language_setting, 67 | "language_key": self.language_key, 68 | "word_set": self.word_set, 69 | "language": self.language 70 | } 71 | json.dump(w_data, fp, indent=2) 72 | fp.close() 73 | 74 | def api(self, action, param = None): 75 | """ 76 | 供给外部调用的api 77 | :param action:请求动作 78 | :param param:携带的参数 79 | """ 80 | if action == mh.A_UPDATALANG: # 语言更新 81 | return self.get_data(param) 82 | 83 | class ModelFile(object): 84 | def __init__(self): 85 | self.file_path = None 86 | pass 87 | 88 | def create_file(self, file_path): 89 | self.file_path = file_path 90 | fp = codecs.open(self.file_path, "w", "utf8") 91 | json.dump({}, fp, indent=2) 92 | fp.close() 93 | 94 | def read(self, file_path): 95 | """ 96 | 读取模型信息 97 | :param read_path: 模型文件所在的路径 98 | :return: 模型各组件的位置信息 99 | """ 100 | self.file_path = file_path 101 | fp = codecs.open(self.file_path, "r", "utf8") 102 | self.model_data = json.load(fp) 103 | fp.close() 104 | # 得到所有的模型组件名称 105 | name_list = self.model_data["model_name_list"].strip().split() 106 | model_info = {} 107 | for name in name_list: 108 | key = name.strip() 109 | model_info[key] = self.model_data[key] 110 | return model_info 111 | 112 | def save(self, model_info, save_path=None): 113 | """ 114 | 保存模型文件 115 | :param save_path:写入目标文件的路径 116 | :param model_info:模型相关的数据 117 | :return:None 118 | """ 119 | if save_path!=None: 120 | self.file_path = save_path 121 | names = [ name.strip() for name in model_info.keys()] 122 | model_name = "" 123 | for name in names: 124 | model_name = model_name+" "+name 125 | w_data = { 126 | "model_name_list":model_name.strip(), 127 | } 128 | for key, info in model_info.items(): 129 | w_data[key] = info 130 | fp = codecs.open(self.file_path, "w", "utf8") 131 | json.dump(w_data, fp, indent=2) 132 | fp.close() 133 | 134 | def __del__(self): 135 | pass 136 | 137 | class ActionFile(object): 138 | def __init__(self): 139 | pass 140 | 141 | def create_file(self, file_path): 142 | self.file_path = file_path 143 | fp = codecs.open(self.file_path, "w", "utf8") 144 | json.dump({}, fp, indent=2) 145 | fp.close() 146 | 147 | def read(self, file_path): 148 | """ 149 | 读取模型信息 150 | :param read_path: 动作组文件所在的路径 151 | :return: 动作组信息 152 | """ 153 | self.file_path = file_path 154 | fp = codecs.open(self.file_path, "r", "utf8") 155 | self.action_data = json.load(fp) 156 | fp.close() 157 | # 得到所有的模型组件名称 158 | num_list = self.action_data["model_num_list"].strip().split() 159 | action_info = {} 160 | for num in num_list: 161 | key = num.strip() 162 | action_info[key] = self.action_data[key] 163 | return action_info 164 | 165 | def save(self, action_info, save_path=None): 166 | """ 167 | 保存模型文件 168 | :param save_path:写入目标文件的路径 169 | :param model_info:模型相关的数据 170 | :return:None 171 | """ 172 | if save_path!=None: 173 | self.file_path = save_path 174 | names = [ name.strip() for name in action_info.keys()] 175 | action_num = "" 176 | for name in names: 177 | action_num = action_num+" "+name 178 | w_data = { 179 | "action_num_list":action_num.strip(), 180 | } 181 | for key, info in action_info.items(): 182 | w_data[key] = info 183 | fp = codecs.open(self.file_path, "w", "utf8") 184 | json.dump(w_data, fp, indent=2) 185 | fp.close() 186 | 187 | def __del__(self): 188 | pass 189 | 190 | class SystemConfig(object): 191 | """ 192 | 系统配置文件类 193 | """ 194 | def __init__(self, filepath): 195 | """ 196 | 系统配置文件类初始化 197 | :param filepath: 配置文件所在目录 198 | """ 199 | self.cfgfile_path = filepath 200 | self.m_sysinfo = {} 201 | self.read() 202 | 203 | def read(self): 204 | """ 205 | 读取系统配置信息 206 | :return: 系统配置信息 207 | """ 208 | fp = codecs.open(self.cfgfile_path, "r", "utf8") 209 | self.m_sysinfo = json.load(fp) 210 | fp.close() 211 | return self.m_sysinfo 212 | 213 | def api(self, action, param): 214 | if action == None: 215 | pass 216 | 217 | def __del__(self): 218 | pass 219 | 220 | if __name__ == "__main__": 221 | lang = Language("./language") # first 222 | print(lang["language_set"]) -------------------------------------------------------------------------------- /src/file_trans_manager.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import os 3 | import struct 4 | import codecs 5 | import random 6 | from robotsocket import * 7 | 8 | 9 | class FileTransManager(object): 10 | 11 | def __init__(self, ): 12 | self.ContexMaxNum = 256 13 | self.FileInfoDict = {} 14 | self.DataRear = b'\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff' # 帧尾 15 | # status 文件数据包的状态 DataHeader的第二字节 16 | self.STATUS_EDLE = b'\x00' # 文件空闲状态 17 | self.STATUS_START = b'\x01' # 文件传输的开始的标志(文件整体的信息) 18 | self.STATUS_NORMAL = b'\x02' # 文件的传输 19 | self.STATUS_END = b'\x03' # 文件的最后一包 20 | 21 | self.recvBuf = b'' 22 | # 每次读取的包大小 23 | self.sendPacketMaxSize = 1024*128 24 | self.time1 = 0 25 | 26 | def deal_recv_data(self, recvDat): 27 | try: 28 | self.recvBuf = self.recvBuf + recvDat 29 | while True: 30 | nowDat = self.recvBuf.split(self.DataRear) 31 | if len(nowDat) < 2: 32 | return None 33 | else: 34 | nowDat = nowDat[0] 35 | self.recvBuf = self.recvBuf[len(nowDat) + len(self.DataRear):] 36 | 37 | crn = nowDat[0].to_bytes(1, byteorder='little') 38 | status = nowDat[1].to_bytes(1, byteorder='little') 39 | useData = nowDat[2:] 40 | if status == self.STATUS_START: # 判断是否是文件接收状态 41 | filename, filesize = struct.unpack('128sl', useData) 42 | filename = filename.strip(b'\00').decode() 43 | fp = codecs.open("./recv_" + filename, 'wb') 44 | self.FileInfoDict[crn] = {"fp": fp, "name": filename, "size": filesize} 45 | self.time1 = time.time() 46 | elif crn in self.FileInfoDict.keys(): 47 | if status == self.STATUS_NORMAL: 48 | self.FileInfoDict[crn]["fp"].write(useData) 49 | elif status == self.STATUS_END: 50 | self.FileInfoDict[crn]["fp"].close() 51 | del self.FileInfoDict[crn] 52 | print("File is recv finish.") 53 | print(time.time() - self.time1) 54 | except Exception as err: 55 | print(err) 56 | 57 | def send_file(self, fileName, send_func, send_finish_func=None): 58 | # 创建发送任务 59 | run_thread = threading.Thread(target=create_send_task, args=(fileName, send_func, send_finish_func)) 60 | run_thread.start() 61 | 62 | def create_send_task(self, fileName, send_func, send_finish_func=None): 63 | # 判断是否为文件 64 | if os.path.isfile(fileName): 65 | fp = codecs.open(fileName, "rb") 66 | crn = int(self.genCrn()).to_bytes(1, byteorder='little') 67 | # 定义文件头信息,包含文件名和文件大小 68 | fhead_info = struct.pack('128sl', os.path.basename(fileName).encode('utf-8'), os.stat(fileName).st_size) 69 | send_func(crn + self.STATUS_START + fhead_info + self.DataRear) 70 | while True: 71 | data = fp.read(self.sendPacketMaxSize) 72 | if data == None or data == b'': 73 | send_func(crn + self.STATUS_END + self.DataRear) 74 | if None != send_finish_func: 75 | send_finish_func("succ") 76 | print("Send succ.") 77 | break 78 | else: 79 | send_func(crn + self.STATUS_NORMAL + data + self.DataRear) 80 | 81 | def genCrn(self): 82 | crn = random.randint(0, self.ContexMaxNum - 1) 83 | while True: 84 | if len(self.FileInfoDict) == self.ContexMaxNum: 85 | return None 86 | if crn not in self.FileInfoDict.keys(): 87 | return crn 88 | else: 89 | crn = (crn + 1) % self.ContexMaxNum 90 | 91 | def __del__(self): 92 | for keys in self.FileInfoDict.keys(): 93 | self.FileInfoDict[keys]["fp"].close() 94 | del self.FileInfoDict[keys] 95 | 96 | 97 | if __name__ == "__main__": 98 | # This is demo 99 | 100 | filemanager = FileTransManager() 101 | 102 | # 服务器端范例 103 | def mySerRecvHandle(dat, addr): # 接收函数 104 | filemanager.deal_recv_data(dat) 105 | #dat = ("Server recv %s from %s\n" % (dat, addr)).encode(encoding="utf-8") 106 | #print(dat) 107 | 108 | # 初始化端口并设置接收数据的函数(当接收到数据,自动被调用) 109 | sersocket = RobotSocketServer("192.168.1.33", 5555, mySerRecvHandle, max_bind=10) 110 | sersocket.start() # socket开始工作 111 | import time 112 | time.sleep(0.2) 113 | 114 | # 客户端范例 115 | def myClientRecvHandle(dat): # 接收函数 116 | dat = ("Client recv %s\n" % dat).encode(encoding="utf-8") 117 | print(dat) 118 | 119 | # 初始化端口并设置接收数据的函数(当接收到数据,自动被调用) 120 | clientsocket = RobotSocketClient("192.168.1.33", 5555, myClientRecvHandle) 121 | clientsocket.start() # socket开始工作 122 | 123 | filemanager.create_send_task("./MalhamStars.jpg", clientsocket.send_to_ser) -------------------------------------------------------------------------------- /src/init.cfg: -------------------------------------------------------------------------------- 1 | { 2 | "system_init": { 3 | "imagepath": "../img/", 4 | "language": "./language", 5 | "ver": "1.0" 6 | }, 7 | "gui": { 8 | "currency": "amazon iTunes GooglePlay Rakten NANACO WebMoney", 9 | "curchoose": "\u5e01\u79cd\u9009\u62e9", 10 | "remainder": "\u5269\u4f59\u91cf", 11 | "facevalue": "\u9762\u503c\u9009\u62e9", 12 | "price": "\u4ef7\u683c", 13 | "salesrate": "\u9500\u552e\u7387", 14 | "num": "\u5b9e\u9645\u6210\u7ee9", 15 | "percentage": "\u767e\u5206\u6bd4", 16 | "fields": "\u5e01\u79cd \u5269\u4f59 \u9762\u503c \u552e\u4ef7 \u9500\u552e\u7387 \u5b9e\u9645\u6210\u7ee9 \u767e\u5206\u6bd4 \u72b6\u6001" 17 | }, 18 | "taskinfo": { 19 | "tasknum": "2", 20 | "updatatime": "10", 21 | "task1": { 22 | "curchoose": "GooglePlay", 23 | "remainder": "0", 24 | "facevalue": "10000-10000", 25 | "price": "8500-8800", 26 | "salesrate": "0", 27 | "num": "0-0", 28 | "percentage": "0-0" 29 | }, 30 | "task2": { 31 | "curchoose": "GooglePlay", 32 | "remainder": "1", 33 | "facevalue": "3000-3000", 34 | "price": "2610-2700", 35 | "salesrate": "0", 36 | "num": "0-0", 37 | "percentage": "0-0" 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/language: -------------------------------------------------------------------------------- 1 | { 2 | "language_set": "\u4e2d\u6587", 3 | "language_key": "English, \u4e2d\u6587, \u65e5\u672c\u8a9e, Fran\u00e7ais, German", 4 | "word_set": { 5 | "Windows": "Title", 6 | "Menu": "Menu, File, Setting, Model, Action, Create, Open, Save, SaveAs, Exit, Help, Language, View, FileManager, TransInfo, About, Registration", 7 | "Action": "Add, Del", 8 | "MassageBox": "UserCode, UserCodeText", 9 | "Connect": "Connect, DisConnect, UartNo, BaudRate, IpAddr, Port", 10 | "SysOut": "title" 11 | }, 12 | "language": { 13 | "English": { 14 | "Windows": "Robot Universal Control Platform", 15 | "Menu": "Menu, File, Settings, Model, Action, New, Open, Save, Save As, Exit, Help, Language, View, FileManager, TransInfo, About, Registration", 16 | "Action": "Add, Del", 17 | "MassageBox": "Enter your user ID, which is currently open for use", 18 | "Connect": "Connect, DisConnect, UartNo, BaudRate, IpAddr, Port", 19 | "SysOut": "System Out" 20 | }, 21 | "\u4e2d\u6587": { 22 | "Windows": "\u673a\u5668\u4eba\u901a\u7528\u63a7\u5236\u5e73\u53f0", 23 | "Menu": "\u83dc\u5355, \u6587\u4ef6, \u8bbe\u7f6e, \u6a21\u578b, \u52a8\u4f5c, \u65b0\u5efa, \u6253\u5f00, \u4fdd\u5b58, \u53e6\u5b58\u4e3a, \u9000\u51fa, \u5e2e\u52a9, \u8bed\u8a00, \u89c6\u56fe, \u6587\u4ef6\u7ba1\u7406, \u4f20\u8f93\u65e5\u5fd7, \u5173\u4e8e, \u6ce8\u518c", 24 | "Action": "\u6dfb\u52a0\u8235\u673a, \u5220\u9664\u8235\u673a", 25 | "MassageBox": "\u8f93\u5165\u60a8\u7684\u7528\u6237\u8bc6\u522b\u7801, \u76ee\u524d\u5f00\u653e\u4f7f\u7528", 26 | "Connect": "\u8fde\u63a5, \u65ad\u5f00\u8fde\u63a5, \u4e32\u53e3\u53f7, \u6ce2\u7279\u7387, IP\u5730\u5740, \u7aef\u53e3", 27 | "SysOut": "\u7cfb\u7edf\u4fe1\u606f\u8f93\u51fa" 28 | }, 29 | "\u65e5\u672c\u8a9e": { 30 | "Windows": "\u30ed\u30dc\u30c3\u30c8\u5171\u901a\u5236\u5fa1\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0", 31 | "Menu": "\u30e1\u30cb\u30e5\u30fc, \u30d5\u30a1\u30a4\u30eb, \u8a2d\u5b9a, \u30e2\u30c7\u30eb, \u30a2\u30af\u30b7\u30e7\u30f3, \u65b0\u898f\u4f5c\u6210, \u958b\u304f, \u4fdd\u5b58, \u540d\u524d\u3092\u4ed8\u3051\u3066\u4fdd\u5b58, \u7d42\u4e86, \u30d8\u30eb\u30d7, \u8a00\u8a9e, \u8868\u793a, \u30d5\u30a1\u30a4\u30eb\u7ba1\u7406, \u8ee2\u9001\u30ed\u30b0, \u306b\u3064\u3044\u3066, \u767b\u9332", 32 | "Action": "\u8ffd\u52a0, \u524a\u9664", 33 | "MassageBox": "\u30e6\u30fc\u30b6\u30fc\u8b58\u5225\u30b3\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044, \u73fe\u5728\u5229\u7528\u53ef\u80fd\u3067\u3059", 34 | "Connect": "\u63a5\u7d9a, \u5207\u65adk, \u30b7\u30ea\u30a2\u30eb\u756a\u53f7, \u30dc\u30fc\u30ec\u30fc\u30c8, IP\u30a2\u30c9\u30ec\u30b9, \u30dd\u30fc\u30c8", 35 | "SysOut": "\u30b7\u30b9\u30c6\u30e0\u3058\u3087\u3046\u307b\u3046\u3057\u3085\u3064\u308a\u3087\u304f" 36 | }, 37 | "Fran\u00e7ais": { 38 | "Windows": "Plate-forme de contr\u00f4le universel de robot", 39 | "Menu": "menu, fichier, param\u00e8tre, mod\u00e8le, action, nouveau, ouvert, sauvegard\u00e9, enregistr\u00e9 comme, sortie, aide, langue, Vue, Gestion de fichiers, journal de transfert, \u00c0 propos de, inscription", 40 | "Action": "Ajouter, Supprimer", 41 | "MassageBox": "Entrez votre num\u00e9ro d'identification de l'utilisateur, actuellement ouvert", 42 | "Connect": "Connectez-vous, d\u00e9connectez-vous, num\u00e9ro de port s\u00e9rie, d\u00e9bit en bauds, adresse IP, port", 43 | "SysOut": "Sortie d'informations syst\u00e8me" 44 | }, 45 | "German": { 46 | "Windows": "Universelle Steuerungsplattform f\u00fcr Roboter", 47 | "Menu": "Men\u00fcs, Dateien, Einstellungen, Modelle, Aktionen, Neu, \u00d6ffnen, Speichern, Speichern unter, Beenden, Hilfe, Sprachen, View, Dateiverwaltung, \u00dcbertragungsprotokolle, About, registrierung", 48 | "Action": "Hinzuf\u00fcgen, L\u00f6schen", 49 | "MassageBox": "Geben Sie Ihren Benutzeridentifikationscode ein, der derzeit verf\u00fcgbar ist", 50 | "Connect": "Connection, disconnection, serial port number, baud rate, IP address, port", 51 | "SysOut": "Ausgabe von Systeminformationen" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/language.bak: -------------------------------------------------------------------------------- 1 | { 2 | "language_set":"中文", 3 | 4 | "language_key": "English, 中文, 日本語, Français, German", 5 | 6 | "word_set": { 7 | "Windows":"Title", 8 | "Menu":"Menu, File, Setting, Model, Action, Create, Open, Save, SaveAs, Exit, Help, Language, View, FileManager, TransInfo, About, Registration", 9 | "Action":"Add, Del", 10 | "MassageBox":"UserCode, UserCodeText", 11 | "Connect":"Connect, DisConnect, UartNo, BaudRate, IpAddr, Port", 12 | "SysOut":"title" 13 | }, 14 | 15 | "language": { 16 | "English":{ 17 | "Windows":"Robot Universal Control Platform", 18 | "Menu":"Menu, File, Settings, Model, Action, New, Open, Save, Save As, Exit, Help, Language, View, FileManager, TransInfo, About, Registration", 19 | "Action":"Add, Del", 20 | "MassageBox":"Enter your user ID, which is currently open for use", 21 | "Connect":"Connect, DisConnect, UartNo, BaudRate, IpAddr, Port", 22 | "SysOut":"System Out" 23 | }, 24 | 25 | "中文":{ 26 | "Windows":"机器人通用控制平台", 27 | "Menu":"菜单, 文件, 设置, 模型, 动作, 新建, 打开, 保存, 另存为, 退出, 帮助, 语言, 视图, 文件管理, 传输日志, 关于, 注册", 28 | "Action":"添加舵机, 删除舵机", 29 | "MassageBox":"输入您的用户识别码, 目前开放使用", 30 | "Connect":"连接, 断开连接, 串口号, 波特率, IP地址, 端口", 31 | "SysOut":"系统信息输出" 32 | }, 33 | 34 | "日本語":{ 35 | "Windows":"ロボット共通制御プラットフォーム", 36 | "Menu":"メニュー, ファイル, 設定, モデル, アクション, 新規作成, 開く, 保存, 名前を付けて保存, 終了, ヘルプ, 言語, 表示, ファイル管理, 転送ログ, について, 登録", 37 | "Action":"追加, 削除", 38 | "MassageBox":"ユーザー識別コードを入力してください, 現在利用可能です", 39 | "Connect":"接続, 切断k, シリアル番号, ボーレート, IPアドレス, ポート", 40 | "SysOut":"システムじょうほうしゅつりょく" 41 | }, 42 | 43 | "Français":{ 44 | "Windows":"Plate-forme de contrôle universel de robot", 45 | "Menu":"menu, fichier, paramètre, modèle, action, nouveau, ouvert, sauvegardé, enregistré comme, sortie, aide, langue, Vue, Gestion de fichiers, journal de transfert, À propos de, inscription", 46 | "Action":"Ajouter, Supprimer", 47 | "MassageBox":"Entrez votre numéro d'identification de l'utilisateur, actuellement ouvert", 48 | "Connect":"Connectez-vous, déconnectez-vous, numéro de port série, débit en bauds, adresse IP, port", 49 | "SysOut":"Sortie d'informations système" 50 | }, 51 | 52 | "German":{ 53 | "Windows":"Universelle Steuerungsplattform für Roboter", 54 | "Menu":"Menüs, Dateien, Einstellungen, Modelle, Aktionen, Neu, Öffnen, Speichern, Speichern unter, Beenden, Hilfe, Sprachen, View, Dateiverwaltung, Übertragungsprotokolle, About, registrierung", 55 | "Action":"Hinzufügen, Löschen", 56 | "MassageBox":"Geben Sie Ihren Benutzeridentifikationscode ein, der derzeit verfügbar ist", 57 | "Connect":"Connection, disconnection, serial port number, baud rate, IP address, port", 58 | "SysOut":"Ausgabe von Systeminformationen" 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/massagehead.py: -------------------------------------------------------------------------------- 1 | 2 | # 模块名 M_ 3 | M_ALL = "M_ALL" 4 | M_ENGINE = "M_ENGINE" 5 | M_LANGUAGE = "M_LANGUAGE" 6 | M_CTRLMENU = "M_CTRLMENU" 7 | M_SMMODEL = "M_SMMODEL" 8 | M_SMMODEL_MANAGER = "M_SMMODEL_MANAGER" 9 | M_MODEL_FILEMANAGER = "M_MODEL_FILEMANAGER" 10 | M_ACTION_FILEMANAGER = "M_ACTION_FILEMANAGER" 11 | M_SOCKET_MAIN = "M_SOCKET_MAIN" 12 | M_CONNECTOR = "M_CONNECTOR" 13 | M_TREETABLE = "M_TREETABLE" 14 | M_SYSINFO = "M_SYSINFO" 15 | 16 | # 动作类型 A_ 17 | A_UPDATALANG = "A_UPDATALANG" 18 | A_CREATE_MODEL = "A_CREATE_MODEL" 19 | A_DEL_MODEL = "A_DEL_MODEL" 20 | A_SELECT = "A_SELECT" 21 | A_CONNECT = "A_CONNECT" 22 | A_DISCONNECT = "A_DISCONNECT" 23 | A_INFO_PRINT = "A_INFO_PRINT" 24 | A_GET_MODELINFO = "A_GET_MODELINFO" 25 | A_SET_MODELINFO = "A_SET_MODELINFO" 26 | A_SET_MODELNAME = "A_SET_MODELNAME" 27 | A_GET_ACTIONINFO = "A_GET_ACTIONINFO" 28 | A_SET_ACTIONINFO = "A_SET_ACTIONINFO" 29 | A_UPDATA_ALL_MODEL_VAL = "A_UPDATA_ALL_MODEL_VAL" 30 | A_UPDATA_TREE_SELECT_ROW = "A_UPDATA_TREE_SELECT_ROW" 31 | A_SET_TABLEHEAD = "A_SET_TABLEHEAD" 32 | 33 | A_FILE_CREATE = "A_FILE_CREATE" 34 | A_FILE_OPEN = "A_FILE_OPEN" 35 | A_FILE_SAVE = "A_FILE_SAVE" 36 | A_FILE_SAVEAS = "A_FILE_SAVEAS" 37 | -------------------------------------------------------------------------------- /src/robotsocket.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import threading 3 | import socket # socket模块 4 | import time 5 | import ctypes 6 | 7 | 8 | def _async_raise( thread): 9 | """ 10 | 释放进程 11 | :param thread: 进程对象 12 | :param exctype: 13 | :return: 14 | """ 15 | try: 16 | tid = thread.ident 17 | tid = ctypes.c_long(tid) 18 | exctype = SystemExit 19 | """raises the exception, performs cleanup if needed""" 20 | if not inspect.isclass(exctype): 21 | exctype = type(exctype) 22 | res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) 23 | if res == 0: 24 | raise ValueError("invalid thread id") 25 | elif res != 1: 26 | # """if it returns a number greater than one, you're in trouble, 27 | # and you should call it again with exc=NULL to revert the effect""" 28 | ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) 29 | raise SystemError("PyThreadState_SetAsyncExc failed") 30 | except Exception as err: 31 | print(err) 32 | 33 | class RobotSocket(object): 34 | 35 | def __init__(self, ip, port, callback_func=None, name=""): 36 | self._name = name 37 | self._ip = ip 38 | self._port = port 39 | self._callback_func = callback_func 40 | 41 | def close(self): 42 | try: 43 | self.connfd.close() # 关闭连接 44 | self.connfd = None 45 | except Exception as err: 46 | print(err) 47 | 48 | @property 49 | def callback_func(self): 50 | return self._callback_func 51 | 52 | @callback_func.setter 53 | def callback_func(self, callback): 54 | self._callback_func = callback 55 | 56 | def start(self, ): 57 | # override 58 | pass 59 | 60 | def __del__(): 61 | pass 62 | 63 | 64 | class RobotSocketServer(RobotSocket): 65 | # 服务端类 66 | def __init__(self, ip, port, callback_func=None, max_bind=1, name="RobotSocketServer"): 67 | """ 68 | RobotSocketServer类对象的初始化 69 | :param ip: 点分十进制的ip字符串 70 | :param port: 端口号整型数据(0-65535) 71 | :param callback_func: 接收处理函数 72 | :param max_bind: 最大服务数量 73 | :param name: socket实例名称 74 | """ 75 | super().__init__(ip, port, callback_func, name) 76 | self.__sersocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 定义socket类型,网络通信,TCP 77 | self.__sersocket.bind((self._ip, self._port)) # 套接字绑定的IP与端口 78 | self.__max_bind = max_bind 79 | self.__client_link_dict = {} 80 | self.__sersocket.listen(self.__max_bind) # 开始TCP监听 81 | self.__recv_buff = 1024*128 82 | 83 | def start(self): 84 | ''' 85 | 启动socket实例 86 | :return: 87 | ''' 88 | 89 | def scanner(): 90 | while True: 91 | connfd, addr = self.__sersocket.accept() # 接受TCP连接,并返回新的套接字与IP地址 92 | print('Connected by', addr) # 输出客户端的IP地址 93 | run_thread = threading.Thread(target=self.recvfrom_client, args=(connfd, addr)) 94 | run_thread.start() 95 | self.__client_link_dict[addr] = {"fd":connfd, 'pthread':run_thread} 96 | 97 | run_thread = threading.Thread(target=scanner, args=()) 98 | run_thread.start() 99 | 100 | def recvfrom_client(self, connfd, addr): 101 | """ 102 | 客户端连接状态的数据处理 103 | :param connfd: 连接客户端的文件句柄 104 | :param addr: 客户端的地址 105 | :return: 106 | """ 107 | try: 108 | while True: 109 | recv = connfd.recv(self.__recv_buff) # 把接收的数据实例化 110 | if recv == b'': # 断开连接 111 | break 112 | if self.callback_func != None: 113 | self.callback_func(recv, addr) 114 | except Exception as err: 115 | print("This thread was killed, Client disconnected\t->\t", end='') 116 | print(err) 117 | 118 | def send_to_client(self, dat, addr): 119 | """ 120 | 向本次连接的客户端发送数据 121 | :param dat: 要发送的数据(bytes类型) 122 | :param addr: 要发送到的客户端地址 123 | :return: 124 | """ 125 | try: 126 | if addr in self.__client_link_dict.keys(): 127 | self.__client_link_dict["fd"].sendall(dat) 128 | else: 129 | print("Address is no found or disconnect.") 130 | except Exception as err: 131 | print(err) 132 | 133 | def __del__(self): 134 | try: 135 | for conninfo in self.__client_link_dict.items(): 136 | connfd = conninfo["fd"] 137 | connfd.close() # 关闭连接 138 | _async_raise(conninfo['pthread']) 139 | del conninfo 140 | except Exception as err: 141 | print(err) 142 | 143 | 144 | class RobotSocketClient(RobotSocket): 145 | # 客户端类 146 | def __init__(self, ip, port, callback_func=None, disconntime=0.5, name="RobotSocketClient"): 147 | """ 148 | RobotSocket类对象的初始化 149 | :param ip: 点分十进制的ip字符串 150 | :param port: 端口号整型数据(0-65535) 151 | :param callback_func: 接收处理函数 152 | :param name: socket实例名称 153 | """ 154 | super().__init__(ip, port, callback_func, name) 155 | self.__clientsocket = None 156 | self.__connFlag = False # 连接状态 157 | self.__disconntime = disconntime # 掉线重连的时间 158 | self.__recv_buff = 1024*128 159 | 160 | def start(self): 161 | ''' 162 | 启动socket实例 163 | :return: 164 | ''' 165 | 166 | def reconner(): 167 | while True: 168 | try: 169 | addr = (self._ip, self._port) 170 | if False == self.__connFlag: 171 | print("Try to reconnect......") 172 | del self.__clientsocket 173 | self.__clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 定义socket类型,网络通信,TCP 174 | self.__clientsocket.connect(addr) 175 | print('Connected by', addr) # 输出客户端的IP地址 176 | self.__connFlag = True 177 | except Exception as err: 178 | print(err) 179 | time.sleep(self.__disconntime) 180 | 181 | run_thread = threading.Thread(target=reconner, args=()) 182 | run_thread.start() 183 | run_thread = threading.Thread(target=self.recvfrom_ser, args=()) 184 | run_thread.start() 185 | 186 | def recvfrom_ser(self,): 187 | """ 188 | 客户端连接状态的数据处理 189 | :param client: 连接客户端的文件句柄 190 | :param addr: 客户端的地址 191 | :return: 192 | """"" 193 | try: 194 | while True: 195 | try: 196 | if True == self.__connFlag: 197 | recv = self.__clientsocket.recv(self.__recv_buff) # 把接收的数据实例化 198 | if recv == b'': # 断开连接 199 | break 200 | if self.callback_func != None: 201 | self.callback_func(recv) 202 | 203 | except Exception as err: 204 | self.__clientsocket.close() 205 | self.__connFlag = False 206 | print(err) # 发生异常所在的文件 207 | time.sleep(self.__disconntime*0.2) 208 | 209 | except Exception as err: 210 | print(err) 211 | 212 | def send_to_ser(self, dat): 213 | """ 214 | 向本次连接的客户端发送数据 215 | :param dat: 要发送的数据(bytes类型) 216 | :return: 217 | """ 218 | try: 219 | self.__clientsocket.sendall(dat) 220 | except Exception as err: 221 | print(err) 222 | 223 | def __del__(self): 224 | try: 225 | self.__clientsocket.close() # 关闭连接 226 | del self.__clientsocket 227 | self.__clientsocket = None 228 | except Exception as err: 229 | print(err) 230 | 231 | 232 | 233 | if __name__ == "__main__": 234 | # This is demo 235 | 236 | # 服务器端范例 237 | def myRecvHandle(dat, addr): # 接收函数 238 | sersocket.send_to_client(dat, addr) 239 | dat = ("Server recv %s from %s\n" % (dat, addr)).encode(encoding="utf-8") 240 | print(dat) 241 | 242 | # 初始化端口并设置接收数据的函数(当接收到数据,自动被调用) 243 | sersocket = RobotSocketServer("192.168.123.135", 5555, myRecvHandle, max_bind=10) 244 | sersocket.start() # socket开始工作 245 | import time 246 | 247 | while True: 248 | time.sleep(1) 249 | # sersocket.send_to_client(b'Hello \n') 250 | """ 251 | # 客户端范例 252 | def myRecvHandle(dat): # 接收函数 253 | dat = ("Client recv %s\n" % dat).encode(encoding="utf-8") 254 | print(dat) 255 | # 初始化端口并设置接收数据的函数(当接收到数据,自动被调用) 256 | clientsocket = RobotSocketClient("192.168.123.135", 5555, myRecvHandle) 257 | clientsocket.start() # socket开始工作 258 | import time 259 | while True: 260 | dataIn = input("输入要发送给服务端的数据:") 261 | dat = ("%s\n" % dataIn).encode(encoding="utf-8") 262 | clientsocket.send_to_ser(dat) 263 | """ -------------------------------------------------------------------------------- /src/test.py: -------------------------------------------------------------------------------- 1 | # import tkinter as tk 2 | import re 3 | import tkutils as tku 4 | #from tkinter import ttk 5 | 6 | class Test(): 7 | 8 | def __init__(self, root): 9 | self.m_tree_head_l = ["编号", "状态", "运行", "移动"] # 表头字段 10 | self.m_tree_head_r = ["None"] 11 | self.m_tree_width = [40, 40, 80, 40, 50] # 列对应的宽度 12 | 13 | # 滑动条 14 | self.m_scrollbar_y = tk.Scrollbar(root) 15 | self.m_scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y) 16 | self.m_scrollbar_x = tk.Scrollbar(root) 17 | self.m_scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X) 18 | row_num = 5 # 设置表格显示多少行 19 | self.m_tree = ttk.Treeview(root, show="headings", height=row_num, 20 | xscrollcommand=self.m_scrollbar_x.set, 21 | yscrollcommand=self.m_scrollbar_y.set, 22 | selectmode=tk.BROWSE) 23 | self.m_scrollbar_y.config(command=self.m_tree.yview, orient=tk.VERTICAL) 24 | self.m_scrollbar_x.config(command=self.m_tree.xview, orient=tk.HORIZONTAL) 25 | self.m_scrollbar_y.update() 26 | self.m_scrollbar_x.update() 27 | self.m_tree["columns"] = tuple(self.m_tree_head_l + self.m_tree_head_r) 28 | # 以下代码设置表格的行高 29 | rowheight = 20 30 | style = ttk.Style() 31 | style.configure('Treeview', rowheight=rowheight) # repace 40 with whatever you need 32 | self.updata_table_head(self.m_tree_head_l+self.m_tree_head_r, self.m_tree_width) 33 | 34 | self.m_tree.pack(side=tk.LEFT, expand = True, fill = tk.BOTH) # 设置表格最大化 35 | self.updata_table_head(self.m_tree_head_l+["HA"]+self.m_tree_head_r, 36 | self.m_tree_width[:2]+[100]+self.m_tree_width[2:]) 37 | 38 | def updata_table_head(self, head_list, width_list): 39 | """ 40 | 设置表头属性名称 41 | :param head_list: 表头名称 42 | :param width_list: 表头名称对应列的宽度 43 | :return: None 44 | """ 45 | head_len = len(head_list) 46 | # width_list[head_len-1] = self.m_width-sum( width_list[:head_len] ) 47 | print(width_list) 48 | #self.m_tree.configure(columns=head_list) 49 | self.m_tree["columns"] = tuple(head_list) 50 | for col, width in zip(head_list, width_list): 51 | print(col, width, end=" ") 52 | self.m_tree.column(col, width=width, anchor="center") # 设置列 53 | self.m_tree.heading(col, text=col) # 设置显示的表头名 54 | print() 55 | 56 | class Solution: 57 | def lengthOfLongestSubstring(self, s: str) -> int: 58 | myset = set() 59 | length = len(s) 60 | max_len = 0 61 | start = 0 62 | end = 0 63 | for pos in range(length): 64 | if pos!= 0: 65 | myset.remove(s[pos-1]) 66 | while endend-pos else end-pos 70 | return max_len 71 | 72 | if __name__ == "__main__": 73 | 74 | text = Solution() 75 | print( text.lengthOfLongestSubstring("abcabcbb") ) 76 | 77 | root = tk.Tk() # 创建窗口对象的背景色 78 | root.title("机器人通用控制平台") #窗口名 79 | root.geometry('250x650+10+10') 80 | root.iconbitmap("./img/favicon_64.ico") # 窗体图标 81 | # root.resizable(False, False) # 设置窗体不可改变大小 82 | engine = Test(root) 83 | tku.center_window(root) # 将窗体移动到屏幕中央 84 | # 进入消息循环 父窗口进入事件循环,可以理解为保持窗口运行,否则界面不展示 85 | root.mainloop() 86 | 87 | """ 88 | 89 | 90 | if __name__ == "__main__": 91 | print("hello") 92 | pass 93 | 94 | """ 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/tkutils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import tkinter as tk 4 | import tkinter.font as tkFont 5 | from PIL import Image, ImageTk # pip3 install pillow 6 | from tkinter import messagebox 7 | 8 | 9 | def show_info(message=""): 10 | messagebox.showinfo("提示框", message) 11 | 12 | 13 | def show_confirm(message=""): 14 | """ 15 | True : yes 16 | False : no 17 | """ 18 | return messagebox.askyesno("确认框", message) 19 | 20 | 21 | def center_window(win, width=None, height=None): 22 | """ 将窗口屏幕居中 """ 23 | screenwidth = win.winfo_screenwidth() 24 | screenheight = win.winfo_screenheight() 25 | if width is None: 26 | width, height = get_window_size(win)[:2] 27 | size = '%dx%d+%d+%d' % (width, height, (screenwidth - width)/2, (screenheight - height)/3) 28 | win.geometry(size) 29 | 30 | 31 | def get_window_size(win, update=True): 32 | """ 获得窗体的尺寸 """ 33 | if update: 34 | win.update() 35 | return win.winfo_width(), win.winfo_height(), win.winfo_x(), win.winfo_y() 36 | 37 | 38 | def tkimg_resized(img, w_box, h_box, keep_ratio=True): 39 | """对图片进行按比例缩放处理""" 40 | w, h = img.size 41 | 42 | if keep_ratio: 43 | if w > h: 44 | width = w_box 45 | height = int(h_box * (1.0 * h / w)) 46 | 47 | if h >= w: 48 | height = h_box 49 | width = int(w_box * (1.0 * w / h)) 50 | else: 51 | width = w_box 52 | height = h_box 53 | 54 | img1 = img.resize((width, height), Image.ANTIALIAS) 55 | tkimg = ImageTk.PhotoImage(img1) 56 | return tkimg 57 | 58 | 59 | def image_label(frame, img, width, height, keep_ratio=True): 60 | """输入图片信息,及尺寸,返回界面组件""" 61 | if isinstance(img, str): 62 | _img = Image.open(img) 63 | else: 64 | _img = img 65 | lbl_image = tk.Label(frame, width=width, height=height) 66 | 67 | tk_img = tkimg_resized(_img, width, height, keep_ratio) 68 | lbl_image.image = tk_img 69 | lbl_image.config(image=tk_img) 70 | return lbl_image 71 | 72 | 73 | def _font(fname="微软雅黑", size=12, bold=tkFont.NORMAL): 74 | """设置字体""" 75 | ft = tkFont.Font(family=fname, size=size, weight=bold) 76 | return ft 77 | 78 | 79 | def _ft(size=12, bold=False): 80 | """极简字体设置函数""" 81 | if bold: 82 | return _font(size=size, bold=tkFont.BOLD) 83 | else: 84 | return _font(size=size, bold=tkFont.NORMAL) 85 | 86 | 87 | def h_seperator(parent, height=2): # height 单位为像素值 88 | """水平分割线, 水平填充 """ 89 | tk.Frame(parent, height=height, bg="whitesmoke").pack(fill=tk.X) 90 | 91 | 92 | def v_seperator(parent, width, bg="whitesmoke"): # width 单位为像素值 93 | """垂直分割线 , fill=tk.Y, 但如何定位不确定,直接返回对象,由容器决定 """ 94 | frame = tk.Frame(parent, width=width, bg=bg) 95 | return frame --------------------------------------------------------------------------------