├── .gitignore ├── LICENSE ├── README.md ├── coord ├── 2032c.dat ├── PW51.dat ├── clarky.dat ├── clarky.png ├── e374.dat ├── e374.png ├── mh18.dat ├── mh20.dat ├── mh22.dat ├── mh30.dat ├── mh32.dat ├── mh42.dat ├── mh43.dat ├── mh45.dat ├── mh60.dat ├── mh61.dat ├── mh62.dat ├── s8035.dat └── s8035.png ├── f18.ico ├── wing-2.pyw └── wing.pyw /.gitignore: -------------------------------------------------------------------------------- 1 | wing.ini 2 | *.bak 3 | -------------------------------------------------------------------------------- /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 | # Wing G-code Generator 2 | 3 | This program generates XYUV G-code for hotwire cutting model airplane wings. 4 | This program requires Python 3.9 or later to run and will integrate with LinuxCNC's AXIS interface. 5 | 6 | It can also be used alone on any environment (Mac|Win|Linux with Python 3. 7 | 8 | It has a database of airfoils in Selig DAT format. 9 | You can add your own airfoils. 10 | 11 | ## Install 12 | 13 | 1. Download the master.zip file 14 | 1. Unzip it anywhere you want it for Windows, or into your NC files folder in LinuxCNC 15 | 1. 16 | 1. Windows: double click wing.pyw to run the program (after you install Python3 of course) 17 | 1. LinuxCNC: rename the file wing.pyw to wing.py and open it in LinuxCNC just like any Gcode file 18 | 1. On first run you will need to define the NC and DAT directories from the Edit menu. 19 | 1. The DAT Directory must point at the unzipped 'coord' directory where all the .dat files are. 20 | 1. The NC Directory can be anywhere convenient to you. All Gcode will be saved there. 21 | 1. Close and re-open the program and you should see the airfoil lists populate. 22 | 23 | ## Usage 24 | 25 | This is a very simple generator. Just fill in the various edit fields and click 'Generate Gcode'. 26 | After that you can 'Write Files' (Windows) or use one of the 'Write to Axis' buttons in LinuxCNC. 27 | 28 | ### The Gcode 29 | You get a xxx-left.nc and an xxx-right.nc where xxx is the name of the model. 30 | You MIGHT get a xxx-both.nc. This file cuts both wings one above the other and is only generated if the foam is 31 | thick enough. Despite some internal checks, you should carefully check that the wings do no touch each other. 32 | In particular when high values of Washout are used the wingtips can collide inside the foam. 33 | The Gcode is very simple and will run on basic controllers like [GRBL](https://rckeith.co.uk/grbl-hotwire-mega-5x-for-cnc-foam-cutters/) up to Mach3 and [LinuxCNC](https://rckeith.co.uk/download/linuxcnc-foam-cutter-hal-file/). 34 | 35 | ### Cutting 36 | Before you cut you need to set the wire heat to suite the feedrate you have set. 37 | You can check this by giving a command like 38 | G1 Y20 V20 F50 39 | (use your feedrate!) and hold a piece of foam in the wire path while the wire moves up. 40 | Adjust wire heat until you are happy. The wire must not touch the foam! Not ever! 41 | 42 | The 0,0 point on the foam block is at the bottom front corner, the corner 'on the table'. 43 | Set 0,0 with the wire cold. 44 | Now jog the wire up and away from the table so it can safely get hot without melting the foam. 45 | Start the cut. There is never any cut actually *at* 0,0 so this is a safe zero setting. 46 | 47 | ### XYUV/XZ/YZ/XYUZ 48 | These options let you choose various forms of output. 49 | * XYUV - the normal selection for a LinuxCNC dual gantry setup 50 | * XZ - run a bow on XZ on a router style machine 51 | * YZ - run a bow on YZ on a router style machine 52 | * XYUZ - run a GRBL controlled dual gantry machine using [4 axis GRBL](https://www.rckeith.co.uk/how-to-build-a-usb-cnc-hot-wire-foam-cutter/). 53 | 54 | -------------------------------------------------------------------------------- /coord/2032c.dat: -------------------------------------------------------------------------------- 1 | 20-32C AIRFOIL 2 | 1.000000 0.001600 3 | 0.950000 0.012400 4 | 0.900000 0.022900 5 | 0.800000 0.042800 6 | 0.700000 0.061000 7 | 0.600000 0.077100 8 | 0.500000 0.090500 9 | 0.400000 0.100200 10 | 0.300000 0.104800 11 | 0.250000 0.104400 12 | 0.200000 0.101300 13 | 0.150000 0.093400 14 | 0.100000 0.078000 15 | 0.075000 0.066400 16 | 0.050000 0.051300 17 | 0.025000 0.031700 18 | 0.012500 0.019300 19 | 0.000000 0.000000 20 | 0.012500 -0.005000 21 | 0.025000 -0.004200 22 | 0.050000 -0.001000 23 | 0.075000 0.002800 24 | 0.100000 0.006800 25 | 0.150000 0.014500 26 | 0.200000 0.021700 27 | 0.250000 0.028200 28 | 0.300000 0.033300 29 | 0.400000 0.038500 30 | 0.500000 0.038600 31 | 0.600000 0.035000 32 | 0.700000 0.028600 33 | 0.800000 0.020200 34 | 0.900000 0.010000 35 | 0.950000 0.004400 36 | 1.000000 -0.001600 37 | -------------------------------------------------------------------------------- /coord/PW51.dat: -------------------------------------------------------------------------------- 1 | PW51 2 | 1.00000 0.00047 3 | 0.99656 0.00054 4 | 0.99060 0.00076 5 | 0.98371 0.00112 6 | 0.97605 0.00158 7 | 0.96787 0.00214 8 | 0.95940 0.00276 9 | 0.95075 0.00345 10 | 0.94200 0.00417 11 | 0.93320 0.00493 12 | 0.92435 0.00571 13 | 0.91549 0.00651 14 | 0.90661 0.00733 15 | 0.89772 0.00817 16 | 0.88881 0.00902 17 | 0.87990 0.00989 18 | 0.87098 0.01077 19 | 0.86205 0.01166 20 | 0.85312 0.01256 21 | 0.84417 0.01347 22 | 0.83522 0.01438 23 | 0.82626 0.01530 24 | 0.81730 0.01622 25 | 0.80833 0.01714 26 | 0.79935 0.01807 27 | 0.79038 0.01900 28 | 0.78140 0.01993 29 | 0.77243 0.02086 30 | 0.76345 0.02179 31 | 0.75447 0.02272 32 | 0.74550 0.02365 33 | 0.73652 0.02458 34 | 0.72754 0.02551 35 | 0.71857 0.02644 36 | 0.70959 0.02737 37 | 0.70062 0.02830 38 | 0.69165 0.02922 39 | 0.68269 0.03014 40 | 0.67373 0.03106 41 | 0.66478 0.03197 42 | 0.65582 0.03288 43 | 0.64687 0.03378 44 | 0.63792 0.03468 45 | 0.62897 0.03557 46 | 0.62003 0.03645 47 | 0.61109 0.03733 48 | 0.60215 0.03820 49 | 0.59321 0.03906 50 | 0.58427 0.03991 51 | 0.57534 0.04076 52 | 0.56641 0.04159 53 | 0.55748 0.04241 54 | 0.54856 0.04323 55 | 0.53964 0.04403 56 | 0.53073 0.04482 57 | 0.52182 0.04559 58 | 0.51291 0.04635 59 | 0.50400 0.04710 60 | 0.49509 0.04783 61 | 0.48619 0.04854 62 | 0.47729 0.04925 63 | 0.46840 0.04993 64 | 0.45951 0.05060 65 | 0.45063 0.05124 66 | 0.44174 0.05187 67 | 0.43287 0.05247 68 | 0.42400 0.05306 69 | 0.41514 0.05362 70 | 0.40628 0.05416 71 | 0.39743 0.05467 72 | 0.38858 0.05516 73 | 0.37973 0.05562 74 | 0.37089 0.05605 75 | 0.36205 0.05646 76 | 0.35322 0.05683 77 | 0.34440 0.05718 78 | 0.33558 0.05749 79 | 0.32677 0.05778 80 | 0.31797 0.05802 81 | 0.30917 0.05824 82 | 0.30038 0.05841 83 | 0.29160 0.05855 84 | 0.28284 0.05865 85 | 0.27409 0.05871 86 | 0.26535 0.05873 87 | 0.25662 0.05870 88 | 0.24790 0.05862 89 | 0.23920 0.05850 90 | 0.23052 0.05833 91 | 0.22185 0.05810 92 | 0.21322 0.05782 93 | 0.20460 0.05748 94 | 0.19602 0.05708 95 | 0.18747 0.05661 96 | 0.17896 0.05607 97 | 0.17047 0.05545 98 | 0.16200 0.05477 99 | 0.15357 0.05400 100 | 0.14519 0.05316 101 | 0.13687 0.05223 102 | 0.12860 0.05121 103 | 0.12040 0.05009 104 | 0.11227 0.04886 105 | 0.10421 0.04753 106 | 0.09623 0.04609 107 | 0.08835 0.04454 108 | 0.08061 0.04287 109 | 0.07301 0.04108 110 | 0.06559 0.03917 111 | 0.05839 0.03712 112 | 0.05145 0.03496 113 | 0.04482 0.03268 114 | 0.03859 0.03031 115 | 0.03283 0.02790 116 | 0.02763 0.02550 117 | 0.02305 0.02316 118 | 0.01909 0.02095 119 | 0.01573 0.01887 120 | 0.01289 0.01696 121 | 0.01051 0.01518 122 | 0.00851 0.01353 123 | 0.00683 0.01199 124 | 0.00540 0.01055 125 | 0.00420 0.00919 126 | 0.00319 0.00790 127 | 0.00234 0.00668 128 | 0.00163 0.00551 129 | 0.00105 0.00440 130 | 0.00060 0.00332 131 | 0.00027 0.00228 132 | 0.00006 0.00129 133 | -0.00001 0.00033 134 | 0.00005 -0.00062 135 | 0.00028 -0.00159 136 | 0.00068 -0.00256 137 | 0.00126 -0.00350 138 | 0.00202 -0.00441 139 | 0.00294 -0.00530 140 | 0.00402 -0.00617 141 | 0.00527 -0.00705 142 | 0.00671 -0.00793 143 | 0.00837 -0.00885 144 | 0.01029 -0.00979 145 | 0.01253 -0.01076 146 | 0.01517 -0.01179 147 | 0.01829 -0.01287 148 | 0.02201 -0.01402 149 | 0.02642 -0.01523 150 | 0.03161 -0.01649 151 | 0.03756 -0.01776 152 | 0.04419 -0.01900 153 | 0.05138 -0.02019 154 | 0.05898 -0.02128 155 | 0.06689 -0.02230 156 | 0.07501 -0.02322 157 | 0.08327 -0.02406 158 | 0.09163 -0.02482 159 | 0.10009 -0.02550 160 | 0.10862 -0.02612 161 | 0.11721 -0.02668 162 | 0.12584 -0.02718 163 | 0.13451 -0.02762 164 | 0.14323 -0.02803 165 | 0.15198 -0.02839 166 | 0.16075 -0.02871 167 | 0.16955 -0.02899 168 | 0.17837 -0.02925 169 | 0.18720 -0.02948 170 | 0.19604 -0.02967 171 | 0.20489 -0.02984 172 | 0.21376 -0.02998 173 | 0.22265 -0.03010 174 | 0.23155 -0.03020 175 | 0.24045 -0.03028 176 | 0.24935 -0.03034 177 | 0.25826 -0.03038 178 | 0.26718 -0.03040 179 | 0.27610 -0.03040 180 | 0.28502 -0.03039 181 | 0.29396 -0.03036 182 | 0.30290 -0.03032 183 | 0.31185 -0.03027 184 | 0.32080 -0.03020 185 | 0.32974 -0.03013 186 | 0.33869 -0.03003 187 | 0.34764 -0.02993 188 | 0.35659 -0.02981 189 | 0.36555 -0.02968 190 | 0.37451 -0.02954 191 | 0.38347 -0.02939 192 | 0.39244 -0.02922 193 | 0.40141 -0.02905 194 | 0.41038 -0.02887 195 | 0.41934 -0.02868 196 | 0.42831 -0.02849 197 | 0.43727 -0.02828 198 | 0.44624 -0.02806 199 | 0.45520 -0.02783 200 | 0.46417 -0.02759 201 | 0.47313 -0.02734 202 | 0.48210 -0.02708 203 | 0.49107 -0.02682 204 | 0.50005 -0.02654 205 | 0.50903 -0.02626 206 | 0.51801 -0.02597 207 | 0.52698 -0.02568 208 | 0.53594 -0.02537 209 | 0.54491 -0.02506 210 | 0.55388 -0.02473 211 | 0.56286 -0.02440 212 | 0.57184 -0.02406 213 | 0.58082 -0.02371 214 | 0.58979 -0.02336 215 | 0.59876 -0.02300 216 | 0.60774 -0.02263 217 | 0.61672 -0.02225 218 | 0.62570 -0.02187 219 | 0.63467 -0.02148 220 | 0.64365 -0.02108 221 | 0.65262 -0.02068 222 | 0.66159 -0.02027 223 | 0.67057 -0.01985 224 | 0.67955 -0.01942 225 | 0.68853 -0.01899 226 | 0.69750 -0.01855 227 | 0.70647 -0.01810 228 | 0.71545 -0.01765 229 | 0.72442 -0.01719 230 | 0.73340 -0.01672 231 | 0.74238 -0.01625 232 | 0.75136 -0.01577 233 | 0.76033 -0.01528 234 | 0.76931 -0.01479 235 | 0.77828 -0.01429 236 | 0.78725 -0.01378 237 | 0.79623 -0.01327 238 | 0.80520 -0.01275 239 | 0.81418 -0.01222 240 | 0.82315 -0.01169 241 | 0.83212 -0.01115 242 | 0.84109 -0.01061 243 | 0.85008 -0.01005 244 | 0.85907 -0.00950 245 | 0.86805 -0.00894 246 | 0.87703 -0.00838 247 | 0.88601 -0.00781 248 | 0.89499 -0.00723 249 | 0.90398 -0.00665 250 | 0.91298 -0.00607 251 | 0.92196 -0.00549 252 | 0.93093 -0.00491 253 | 0.93989 -0.00432 254 | 0.94884 -0.00373 255 | 0.95775 -0.00315 256 | 0.96655 -0.00258 257 | 0.97511 -0.00203 258 | 0.98317 -0.00151 259 | 0.99040 -0.00104 260 | 0.99653 -0.00066 261 | 1.00000 -0.00047 262 | -------------------------------------------------------------------------------- /coord/clarky.dat: -------------------------------------------------------------------------------- 1 | CLARK Y AIRFOIL 2 | 1.0000000 0.000 3 | 0.9900000 0.0029690 4 | 0.9800000 0.0053335 5 | 0.9700000 0.0076868 6 | 0.9600000 0.0100232 7 | 0.9400000 0.0146239 8 | 0.9200000 0.0191156 9 | 0.9000000 0.0235025 10 | 0.8800000 0.0277891 11 | 0.8600000 0.0319740 12 | 0.8400000 0.0360536 13 | 0.8200000 0.0400245 14 | 0.8000000 0.0438836 15 | 0.7800000 0.0476281 16 | 0.7600000 0.0512565 17 | 0.7400000 0.0547675 18 | 0.7200000 0.0581599 19 | 0.7000000 0.0614329 20 | 0.6800000 0.0645843 21 | 0.6600000 0.0676046 22 | 0.6400000 0.0704822 23 | 0.6200000 0.0732055 24 | 0.6000000 0.0757633 25 | 0.5800000 0.0781451 26 | 0.5600000 0.0803480 27 | 0.5400000 0.0823712 28 | 0.5200000 0.0842145 29 | 0.5000000 0.0858772 30 | 0.4800000 0.0873572 31 | 0.4600000 0.0886427 32 | 0.4400000 0.0897175 33 | 0.4200000 0.0905657 34 | 0.4000000 0.0911712 35 | 0.3800000 0.0915212 36 | 0.3600000 0.0916266 37 | 0.3400000 0.0915079 38 | 0.3200000 0.0911857 39 | 0.3000000 0.0906804 40 | 0.2800000 0.0900016 41 | 0.2600000 0.0890840 42 | 0.2400000 0.0878308 43 | 0.2200000 0.0861433 44 | 0.2000000 0.0839202 45 | 0.1800000 0.0810687 46 | 0.1600000 0.0775707 47 | 0.1400000 0.0734360 48 | 0.1200000 0.0686204 49 | 0.1000000 0.0629981 50 | 0.0800000 0.0564308 51 | 0.0600000 0.0487571 52 | 0.0500000 0.0442753 53 | 0.0400000 0.0391283 54 | 0.0300000 0.0330215 55 | 0.0200000 0.0253735 56 | 0.0120000 0.0178581 57 | 0.0080000 0.0137350 58 | 0.0040000 0.0089238 59 | 0.0020000 0.0058025 60 | 0.0010000 0.0037271 61 | 0.0005000 0.0023390 62 | 0.0000000 0.0000000 63 | 0.0005000 -.0046700 64 | 0.0010000 -.0059418 65 | 0.0020000 -.0078113 66 | 0.0040000 -.0105126 67 | 0.0080000 -.0142862 68 | 0.0120000 -.0169733 69 | 0.0200000 -.0202723 70 | 0.0300000 -.0226056 71 | 0.0400000 -.0245211 72 | 0.0500000 -.0260452 73 | 0.0600000 -.0271277 74 | 0.0800000 -.0284595 75 | 0.1000000 -.0293786 76 | 0.1200000 -.0299633 77 | 0.1400000 -.0302404 78 | 0.1600000 -.0302546 79 | 0.1800000 -.0300490 80 | 0.2000000 -.0296656 81 | 0.2200000 -.0291445 82 | 0.2400000 -.0285181 83 | 0.2600000 -.0278164 84 | 0.2800000 -.0270696 85 | 0.3000000 -.0263079 86 | 0.3200000 -.0255565 87 | 0.3400000 -.0248176 88 | 0.3600000 -.0240870 89 | 0.3800000 -.0233606 90 | 0.4000000 -.0226341 91 | 0.4200000 -.0219042 92 | 0.4400000 -.0211708 93 | 0.4600000 -.0204353 94 | 0.4800000 -.0196986 95 | 0.5000000 -.0189619 96 | 0.5200000 -.0182262 97 | 0.5400000 -.0174914 98 | 0.5600000 -.0167572 99 | 0.5800000 -.0160232 100 | 0.6000000 -.0152893 101 | 0.6200000 -.0145551 102 | 0.6400000 -.0138207 103 | 0.6600000 -.0130862 104 | 0.6800000 -.0123515 105 | 0.7000000 -.0116169 106 | 0.7200000 -.0108823 107 | 0.7400000 -.0101478 108 | 0.7600000 -.0094133 109 | 0.7800000 -.0086788 110 | 0.8000000 -.0079443 111 | 0.8200000 -.0072098 112 | 0.8400000 -.0064753 113 | 0.8600000 -.0057408 114 | 0.8800000 -.0050063 115 | 0.9000000 -.0042718 116 | 0.9200000 -.0035373 117 | 0.9400000 -.0028028 118 | 0.9600000 -.0020683 119 | 0.9700000 -.0017011 120 | 0.9800000 -.0013339 121 | 0.9900000 -.0009666 122 | 1.0000000 -.000 123 | -------------------------------------------------------------------------------- /coord/clarky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swarfer/winggcode/53f20cd1f657c92b404d465e06e4f9346fc47140/coord/clarky.png -------------------------------------------------------------------------------- /coord/e374.dat: -------------------------------------------------------------------------------- 1 | Profil 374 Dicke 10.92% 2 | 1.00000 0.00000 3 | 0.99640 0.00045 4 | 0.98610 0.00204 5 | 0.97000 0.00485 6 | 0.94864 0.00846 7 | 0.92214 0.01264 8 | 0.89078 0.01747 9 | 0.85508 0.02297 10 | 0.81560 0.02905 11 | 0.77293 0.03560 12 | 0.72769 0.04246 13 | 0.68053 0.04944 14 | 0.63210 0.05629 15 | 0.58309 0.06269 16 | 0.53398 0.06821 17 | 0.48511 0.07252 18 | 0.43682 0.07544 19 | 0.38939 0.07685 20 | 0.34312 0.07670 21 | 0.29824 0.07507 22 | 0.25510 0.07217 23 | 0.21415 0.06817 24 | 0.17583 0.06319 25 | 0.14053 0.05734 26 | 0.10860 0.05073 27 | 0.08036 0.04351 28 | 0.05605 0.03581 29 | 0.03589 0.02781 30 | 0.02004 0.01973 31 | 0.00862 0.01186 32 | 0.00178 0.00459 33 | 0.00014 -0.00121 34 | 0.00437 -0.00622 35 | 0.01427 -0.01130 36 | 0.02935 -0.01600 37 | 0.04949 -0.02015 38 | 0.07454 -0.02369 39 | 0.10428 -0.02660 40 | 0.13845 -0.02890 41 | 0.17669 -0.03060 42 | 0.21861 -0.03175 43 | 0.26374 -0.03238 44 | 0.31158 -0.03255 45 | 0.36159 -0.03228 46 | 0.41320 -0.03163 47 | 0.46580 -0.03064 48 | 0.51877 -0.02931 49 | 0.57150 -0.02767 50 | 0.62336 -0.02569 51 | 0.67382 -0.02333 52 | 0.72243 -0.02059 53 | 0.76873 -0.01760 54 | 0.81228 -0.01450 55 | 0.85254 -0.01153 56 | 0.88892 -0.00882 57 | 0.92085 -0.00643 58 | 0.94783 -0.00432 59 | 0.96958 -0.00241 60 | 0.98594 -0.00091 61 | 0.99637 -0.00016 62 | 1.00000 0.00000 -------------------------------------------------------------------------------- /coord/e374.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swarfer/winggcode/53f20cd1f657c92b404d465e06e4f9346fc47140/coord/e374.png -------------------------------------------------------------------------------- /coord/mh18.dat: -------------------------------------------------------------------------------- 1 | AIRFOIL MH 18 11.14% 2 | 1.00000 .00000 3 | .99642 -.00003 4 | .98568 .00016 5 | .96798 .00120 6 | .94389 .00367 7 | .91433 .00783 8 | .88018 .01355 9 | .84221 .02055 10 | .80111 .02852 11 | .75752 .03709 12 | .71204 .04591 13 | .66522 .05458 14 | .61758 .06278 15 | .56971 .07010 16 | .52198 .07594 17 | .47432 .07997 18 | .42680 .08236 19 | .37984 .08337 20 | .33399 .08307 21 | .28973 .08148 22 | .24750 .07859 23 | .20765 .07444 24 | .17052 .06910 25 | .13641 .06267 26 | .10557 .05533 27 | .07828 .04726 28 | .05477 .03866 29 | .03521 .02977 30 | .01977 .02088 31 | .00858 .01235 32 | .00184 .00466 33 | .00015 -.00122 34 | .00452 -.00608 35 | .01482 -.01107 36 | .03023 -.01565 37 | .05068 -.01955 38 | .07607 -.02271 39 | .10622 -.02512 40 | .14089 -.02682 41 | .17976 -.02790 42 | .22244 -.02845 43 | .26843 -.02858 44 | .31718 -.02838 45 | .36807 -.02791 46 | .42050 -.02717 47 | .47385 -.02619 48 | .52751 -.02499 49 | .58085 -.02361 50 | .63327 -.02206 51 | .68415 -.02038 52 | .73290 -.01858 53 | .77896 -.01670 54 | .82178 -.01475 55 | .86087 -.01275 56 | .89575 -.01070 57 | .92602 -.00862 58 | .95129 -.00644 59 | .97149 -.00405 60 | .98672 -.00181 61 | .99655 -.00041 62 | 1.00000 .00000 63 | -------------------------------------------------------------------------------- /coord/mh20.dat: -------------------------------------------------------------------------------- 1 | AIRFOIL MH 20 9.02% 2 | 1.00000 .00000 3 | .99663 -.00001 4 | .98661 .00015 5 | .97014 .00083 6 | .94760 .00230 7 | .91950 .00475 8 | .88644 .00822 9 | .84904 .01269 10 | .80797 .01806 11 | .76391 .02416 12 | .71752 .03077 13 | .66946 .03762 14 | .62039 .04443 15 | .57099 .05086 16 | .52179 .05629 17 | .47283 .06029 18 | .42418 .06296 19 | .37627 .06450 20 | .32965 .06496 21 | .28481 .06431 22 | .24218 .06252 23 | .20213 .05960 24 | .16496 .05558 25 | .13096 .05054 26 | .10040 .04465 27 | .07352 .03806 28 | .05056 .03095 29 | .03165 .02352 30 | .01696 .01608 31 | .00662 .00896 32 | .00083 .00266 33 | .00068 -.00230 34 | .00656 -.00675 35 | .01767 -.01127 36 | .03374 -.01534 37 | .05474 -.01877 38 | .08056 -.02148 39 | .11105 -.02348 40 | .14597 -.02481 41 | .18501 -.02555 42 | .22776 -.02580 43 | .27376 -.02568 44 | .32243 -.02527 45 | .37319 -.02462 46 | .42543 -.02375 47 | .47854 -.02269 48 | .53192 -.02145 49 | .58494 -.02008 50 | .63701 -.01858 51 | .68753 -.01700 52 | .73591 -.01534 53 | .78159 -.01364 54 | .82405 -.01192 55 | .86279 -.01018 56 | .89736 -.00845 57 | .92734 -.00673 58 | .95237 -.00496 59 | .97234 -.00308 60 | .98725 -.00136 61 | .99672 -.00031 62 | 1.00000 .00000 63 | -------------------------------------------------------------------------------- /coord/mh22.dat: -------------------------------------------------------------------------------- 1 | AIRFOIL MH 22 7.21% 2 | 1.00000 .00000 3 | .99679 .00014 4 | .98732 .00067 5 | .97197 .00174 6 | .95105 .00329 7 | .92481 .00530 8 | .89360 .00785 9 | .85787 .01095 10 | .81812 .01460 11 | .77493 .01877 12 | .72890 .02340 13 | .68068 .02839 14 | .63095 .03364 15 | .58055 .03895 16 | .53020 .04380 17 | .48016 .04764 18 | .43050 .05043 19 | .38167 .05232 20 | .33422 .05332 21 | .28862 .05338 22 | .24533 .05242 23 | .20468 .05042 24 | .16701 .04740 25 | .13258 .04341 26 | .10164 .03859 27 | .07446 .03309 28 | .05125 .02705 29 | .03215 .02066 30 | .01730 .01419 31 | .00684 .00797 32 | .00092 .00246 33 | .00057 -.00184 34 | .00611 -.00570 35 | .01679 -.00959 36 | .03242 -.01300 37 | .05296 -.01575 38 | .07837 -.01777 39 | .10849 -.01909 40 | .14309 -.01977 41 | .18187 -.01990 42 | .22445 -.01960 43 | .27034 -.01901 44 | .31898 -.01820 45 | .36977 -.01727 46 | .42209 -.01620 47 | .47535 -.01505 48 | .52892 -.01383 49 | .58219 -.01258 50 | .63453 -.01132 51 | .68535 -.01007 52 | .73406 -.00884 53 | .78007 -.00767 54 | .82287 -.00654 55 | .86194 -.00547 56 | .89683 -.00446 57 | .92712 -.00351 58 | .95243 -.00255 59 | .97259 -.00154 60 | .98750 -.00063 61 | .99681 -.00013 62 | 1.00000 .00000 63 | -------------------------------------------------------------------------------- /coord/mh30.dat: -------------------------------------------------------------------------------- 1 | MH 30 Originally developed for electric powered pylon racing models. MH. 2 | 100.000 0.000 3 | 99.675 0.027 4 | 98.717 0.120 5 | 97.166 0.296 6 | 95.065 0.558 7 | 92.461 0.901 8 | 89.400 1.315 9 | 85.934 1.782 10 | 82.110 2.284 11 | 77.979 2.799 12 | 73.584 3.299 13 | 68.965 3.767 14 | 64.163 4.192 15 | 59.222 4.569 16 | 54.193 4.894 17 | 49.137 5.164 18 | 44.107 5.365 19 | 39.151 5.491 20 | 34.319 5.540 21 | 29.663 5.504 22 | 25.224 5.382 23 | 21.046 5.177 24 | 17.172 4.887 25 | 13.635 4.511 26 | 10.464 4.056 27 | 7.683 3.527 28 | 5.312 2.936 29 | 3.364 2.297 30 | 1.849 1.630 31 | 0.772 0.966 32 | 0.142 0.349 33 | 0.000 0.000 34 | 0.025 -0.137 35 | 0.483 -0.550 36 | 1.492 -0.972 37 | 3.007 -1.362 38 | 5.012 -1.703 39 | 7.493 -1.981 40 | 10.431 -2.192 41 | 13.806 -2.332 42 | 17.589 -2.409 43 | 21.742 -2.426 44 | 26.224 -2.391 45 | 30.985 -2.309 46 | 35.975 -2.189 47 | 41.137 -2.036 48 | 46.416 -1.857 49 | 51.751 -1.659 50 | 57.082 -1.450 51 | 62.348 -1.235 52 | 67.489 -1.024 53 | 72.443 -0.822 54 | 77.151 -0.635 55 | 81.555 -0.469 56 | 85.599 -0.329 57 | 89.230 -0.217 58 | 92.399 -0.134 59 | 95.062 -0.076 60 | 97.180 -0.039 61 | 98.724 -0.015 62 | 99.675 -0.003 63 | 100.000 0.000 64 | -------------------------------------------------------------------------------- /coord/mh32.dat: -------------------------------------------------------------------------------- 1 | MH32 (8.71%) 2 | 1.00000 0.00000 3 | 0.99672 0.00035 4 | 0.98706 0.00150 5 | 0.97145 0.00363 6 | 0.95035 0.00676 7 | 0.92423 0.01084 8 | 0.89361 0.01573 9 | 0.85898 0.02123 10 | 0.82086 0.02714 11 | 0.77973 0.03319 12 | 0.73604 0.03910 13 | 0.69017 0.04467 14 | 0.64252 0.04977 15 | 0.59354 0.05432 16 | 0.54374 0.05830 17 | 0.49372 0.06161 18 | 0.44401 0.06409 19 | 0.39503 0.06565 20 | 0.34730 0.06627 21 | 0.30128 0.06586 22 | 0.25734 0.06439 23 | 0.21589 0.06192 24 | 0.17735 0.05844 25 | 0.14203 0.05398 26 | 0.11019 0.04861 27 | 0.08208 0.04245 28 | 0.05792 0.03562 29 | 0.03783 0.02829 30 | 0.02193 0.02067 31 | 0.01026 0.01308 32 | 0.00289 0.00595 33 | 0.00100 0.00004 34 | 0.00000 0.00000 35 | 0.00281 -0.00462 36 | 0.01171 -0.00882 37 | 0.02583 -0.01270 38 | 0.04500 -0.01604 39 | 0.06906 -0.01873 40 | 0.09783 -0.02074 41 | 0.13106 -0.02206 42 | 0.16847 -0.02271 43 | 0.20969 -0.02274 44 | 0.25432 -0.02223 45 | 0.30188 -0.02126 46 | 0.35184 -0.01990 47 | 0.40366 -0.01824 48 | 0.45647 -0.01634 49 | 0.51048 -0.01429 50 | 0.56428 -0.01216 51 | 0.61750 -0.01003 52 | 0.66952 -0.00797 53 | 0.71971 -0.00605 54 | 0.76746 -0.00433 55 | 0.81218 -0.00286 56 | 0.85329 -0.00169 57 | 0.89023 -0.00082 58 | 0.92250 -0.00026 59 | 0.94964 0.00003 60 | 0.97125 0.00012 61 | 0.98701 0.00011 62 | 0.99670 0.00005 63 | 1.00000 0.00000 64 | -------------------------------------------------------------------------------- /coord/mh42.dat: -------------------------------------------------------------------------------- 1 | MH 42 8.94 % fun and slope soaring, pre-F3B (cl(cd_min)=0.2) 2 | 1.00000 .00000 3 | .99672 .00017 4 | .98700 .00078 5 | .97117 .00204 6 | .94958 .00406 7 | .92268 .00688 8 | .89093 .01047 9 | .85488 .01479 10 | .81506 .01970 11 | .77203 .02508 12 | .72638 .03077 13 | .67870 .03658 14 | .62959 .04234 15 | .57966 .04779 16 | .52944 .05266 17 | .47937 .05672 18 | .42989 .05982 19 | .38139 .06185 20 | .33428 .06273 21 | .28896 .06247 22 | .24580 .06105 23 | .20519 .05854 24 | .16747 .05498 25 | .13297 .05045 26 | .10199 .04506 27 | .07478 .03893 28 | .05156 .03220 29 | .03251 .02503 30 | .01773 .01764 31 | .00727 .01033 32 | .00122 .00357 33 | .00036 -.00179 34 | .00530 -.00638 35 | .01574 -.01104 36 | .03121 -.01540 37 | .05152 -.01925 38 | .07651 -.02243 39 | .10601 -.02479 40 | .13992 -.02634 41 | .17796 -.02719 42 | .21973 -.02746 43 | .26477 -.02721 44 | .31258 -.02651 45 | .36260 -.02541 46 | .41430 -.02396 47 | .46710 -.02222 48 | .52040 -.02027 49 | .57359 -.01816 50 | .62605 -.01595 51 | .67719 -.01369 52 | .72645 -.01143 53 | .77324 -.00928 54 | .81699 -.00730 55 | .85715 -.00554 56 | .89318 -.00404 57 | .92462 -.00281 58 | .95102 -.00184 59 | .97201 -.00110 60 | .98731 -.00051 61 | .99676 -.00013 62 | 1.00000 .00000 63 | -------------------------------------------------------------------------------- /coord/mh43.dat: -------------------------------------------------------------------------------- 1 | MH 43 8.50 % pylon racing, small electric powered models 2 | 1.00000 .00000 3 | .99678 .00014 4 | .98723 .00065 5 | .97160 .00169 6 | .95021 .00339 7 | .92343 .00579 8 | .89169 .00892 9 | .85549 .01279 10 | .81542 .01741 11 | .77217 .02267 12 | .72640 .02833 13 | .67872 .03415 14 | .62970 .03987 15 | .57988 .04524 16 | .52977 .04998 17 | .47979 .05389 18 | .43036 .05682 19 | .38188 .05867 20 | .33474 .05940 21 | .28930 .05901 22 | .24598 .05756 23 | .20519 .05510 24 | .16731 .05167 25 | .13267 .04735 26 | .10157 .04222 27 | .07428 .03641 28 | .05102 .03003 29 | .03197 .02325 30 | .01725 .01627 31 | .00689 .00937 32 | .00101 .00305 33 | .00049 -.00201 34 | .00580 -.00647 35 | .01643 -.01100 36 | .03207 -.01522 37 | .05251 -.01894 38 | .07759 -.02199 39 | .10718 -.02425 40 | .14114 -.02569 41 | .17922 -.02646 42 | .22101 -.02665 43 | .26606 -.02634 44 | .31385 -.02559 45 | .36384 -.02445 46 | .41550 -.02297 47 | .46824 -.02122 48 | .52147 -.01926 49 | .57459 -.01717 50 | .62696 -.01498 51 | .67802 -.01275 52 | .72718 -.01053 53 | .77388 -.00844 54 | .81755 -.00651 55 | .85761 -.00483 56 | .89357 -.00341 57 | .92493 -.00228 58 | .95127 -.00141 59 | .97221 -.00078 60 | .98745 -.00034 61 | .99681 -.00008 62 | 1.00000 .00000 63 | -------------------------------------------------------------------------------- /coord/mh45.dat: -------------------------------------------------------------------------------- 1 | MH45 2 | 1.0000000 0.0000000 3 | 0.9966900 -0.0001000 4 | 0.9866900 -0.0002100 5 | 0.9701300 0.0001600 6 | 0.9474600 0.0013000 7 | 0.9191700 0.0033200 8 | 0.8857400 0.0062900 9 | 0.8477500 0.0102800 10 | 0.8059000 0.0153600 11 | 0.7610700 0.0214000 12 | 0.7140500 0.0280300 13 | 0.6654700 0.0348800 14 | 0.6158700 0.0415400 15 | 0.5656900 0.0476800 16 | 0.5153200 0.0530600 17 | 0.4651600 0.0575500 18 | 0.4156400 0.0610800 19 | 0.3672300 0.0635800 20 | 0.3203900 0.0649800 21 | 0.2755800 0.0652300 22 | 0.2331800 0.0642500 23 | 0.1935300 0.0620300 24 | 0.1569100 0.0586200 25 | 0.1236300 0.0541000 26 | 0.0939500 0.0485800 27 | 0.0681300 0.0421800 28 | 0.0463400 0.0350000 29 | 0.0286700 0.0272200 30 | 0.0152000 0.0190600 31 | 0.0058800 0.0108800 32 | 0.0007900 0.0032600 33 | 0.0000000 0.0000000 34 | 0.0006800 -0.0027900 35 | 0.0064100 -0.0078800 36 | 0.0178100 -0.0131000 37 | 0.0342100 -0.0181400 38 | 0.0553100 -0.0227700 39 | 0.0808500 -0.0267800 40 | 0.1106500 -0.0299100 41 | 0.1446000 -0.0320600 42 | 0.1825200 -0.0332900 43 | 0.2240800 -0.0336600 44 | 0.2689100 -0.0333000 45 | 0.3165400 -0.0322900 46 | 0.3664600 -0.0307300 47 | 0.4181600 -0.0287500 48 | 0.4710400 -0.0264600 49 | 0.5244900 -0.0239900 50 | 0.5778600 -0.0214300 51 | 0.6304900 -0.0188800 52 | 0.6817400 -0.0164000 53 | 0.7309500 -0.0140300 54 | 0.7775400 -0.0117900 55 | 0.8209400 -0.0097100 56 | 0.8606200 -0.0078200 57 | 0.8960700 -0.0061300 58 | 0.9268600 -0.0046500 59 | 0.9525900 -0.0033400 60 | 0.9729300 -0.0021900 61 | 0.9877000 -0.0011300 62 | 0.9968300 -0.0003100 63 | 1.0000000 0.0000000 64 | -------------------------------------------------------------------------------- /coord/mh60.dat: -------------------------------------------------------------------------------- 1 | MH 60 10.08 % flying wings F3B, good cl_max, low c_m 2 | 1.00000 .00000 3 | .99666 -.00011 4 | .98657 -.00023 5 | .96984 .00014 6 | .94692 .00134 7 | .91828 .00354 8 | .88452 .00691 9 | .84641 .01148 10 | .80469 .01708 11 | .76008 .02350 12 | .71329 .03043 13 | .66497 .03752 14 | .61566 .04434 15 | .56577 .05056 16 | .51568 .05594 17 | .46575 .06037 18 | .41641 .06378 19 | .36813 .06615 20 | .32138 .06741 21 | .27662 .06751 22 | .23426 .06640 23 | .19465 .06405 24 | .15809 .06048 25 | .12486 .05576 26 | .09521 .05000 27 | .06937 .04331 28 | .04750 .03581 29 | .02965 .02769 30 | .01589 .01929 31 | .00625 .01098 32 | .00086 .00335 33 | .00063 -.00268 34 | .00634 -.00782 35 | .01760 -.01307 36 | .03387 -.01809 37 | .05490 -.02265 38 | .08046 -.02657 39 | .11036 -.02968 40 | .14441 -.03191 41 | .18237 -.03323 42 | .22396 -.03370 43 | .26880 -.03342 44 | .31644 -.03249 45 | .36637 -.03101 46 | .41806 -.02908 47 | .47094 -.02684 48 | .52438 -.02441 49 | .57774 -.02188 50 | .63036 -.01933 51 | .68160 -.01684 52 | .73082 -.01444 53 | .77743 -.01217 54 | .82085 -.01006 55 | .86054 -.00814 56 | .89601 -.00642 57 | .92681 -.00489 58 | .95255 -.00353 59 | .97290 -.00233 60 | .98767 -.00121 61 | .99682 -.00033 62 | 1.00000 .00000 63 | -------------------------------------------------------------------------------- /coord/mh61.dat: -------------------------------------------------------------------------------- 1 | MH 61 10.28 % flying wings F3B, good cl_max, positive c_m 2 | 1.00000 .00000 3 | .99662 -.00021 4 | .98634 -.00059 5 | .96923 -.00048 6 | .94584 .00055 7 | .91671 .00267 8 | .88248 .00606 9 | .84394 .01072 10 | .80189 .01650 11 | .75708 .02313 12 | .71024 .03031 13 | .66204 .03762 14 | .61302 .04455 15 | .56351 .05077 16 | .51386 .05605 17 | .46445 .06025 18 | .41565 .06329 19 | .36787 .06513 20 | .32154 .06577 21 | .27709 .06518 22 | .23491 .06336 23 | .19534 .06033 24 | .15870 .05614 25 | .12523 .05090 26 | .09521 .04481 27 | .06892 .03804 28 | .04659 .03077 29 | .02843 .02321 30 | .01457 .01560 31 | .00514 .00829 32 | .00031 .00184 33 | .00134 -.00348 34 | .00856 -.00857 35 | .02097 -.01389 36 | .03826 -.01907 37 | .06019 -.02391 38 | .08653 -.02818 39 | .11707 -.03174 40 | .15158 -.03446 41 | .18982 -.03631 42 | .23147 -.03729 43 | .27618 -.03741 44 | .32357 -.03676 45 | .37317 -.03545 46 | .42447 -.03361 47 | .47691 -.03137 48 | .52987 -.02886 49 | .58271 -.02619 50 | .63480 -.02345 51 | .68549 -.02070 52 | .73417 -.01799 53 | .78024 -.01537 54 | .82314 -.01288 55 | .86235 -.01055 56 | .89737 -.00841 57 | .92776 -.00646 58 | .95315 -.00469 59 | .97321 -.00310 60 | .98777 -.00161 61 | .99683 -.00044 62 | 1.00000 .00000 63 | -------------------------------------------------------------------------------- /coord/mh62.dat: -------------------------------------------------------------------------------- 1 | MH 62 9.30 % flying wings F3B, good cl_max, low c_m 2 | 1.00000 .00000 3 | .99672 -.00006 4 | .98684 -.00005 5 | .97051 .00042 6 | .94812 .00163 7 | .92011 .00371 8 | .88703 .00681 9 | .84956 .01096 10 | .80842 .01602 11 | .76428 .02179 12 | .71781 .02802 13 | .66965 .03440 14 | .62035 .04055 15 | .57033 .04619 16 | .52002 .05110 17 | .46981 .05515 18 | .42012 .05830 19 | .37146 .06051 20 | .32431 .06171 21 | .27913 .06186 22 | .23633 .06089 23 | .19629 .05879 24 | .15933 .05557 25 | .12573 .05127 26 | .09577 .04600 27 | .06965 .03985 28 | .04755 .03293 29 | .02954 .02544 30 | .01568 .01766 31 | .00602 .00997 32 | .00067 .00297 33 | .00067 -.00261 34 | .00660 -.00749 35 | .01793 -.01248 36 | .03423 -.01724 37 | .05525 -.02157 38 | .08080 -.02526 39 | .11067 -.02817 40 | .14468 -.03021 41 | .18261 -.03137 42 | .22416 -.03171 43 | .26897 -.03132 44 | .31656 -.03030 45 | .36646 -.02876 46 | .41813 -.02681 47 | .47098 -.02458 48 | .52441 -.02217 49 | .57775 -.01971 50 | .63036 -.01725 51 | .68159 -.01488 52 | .73081 -.01262 53 | .77742 -.01052 54 | .82084 -.00859 55 | .86055 -.00687 56 | .89602 -.00535 57 | .92683 -.00404 58 | .95258 -.00290 59 | .97294 -.00190 60 | .98771 -.00098 61 | .99684 -.00026 62 | 1.00000 .00000 63 | -------------------------------------------------------------------------------- /coord/s8035.dat: -------------------------------------------------------------------------------- 1 | S8035 for RC aerobatic 14% thick 2 | 1.00000 0.00000 3 | 0.99899 0.00005 4 | 0.99604 0.00033 5 | 0.99135 0.00097 6 | 0.98510 0.00198 7 | 0.97745 0.00329 8 | 0.96848 0.00476 9 | 0.95817 0.00629 10 | 0.94646 0.00786 11 | 0.93335 0.00955 12 | 0.91891 0.01137 13 | 0.90320 0.01332 14 | 0.88626 0.01543 15 | 0.86817 0.01770 16 | 0.84901 0.02015 17 | 0.82890 0.02276 18 | 0.80791 0.02550 19 | 0.78613 0.02833 20 | 0.76361 0.03123 21 | 0.74042 0.03418 22 | 0.71664 0.03717 23 | 0.69236 0.04015 24 | 0.66764 0.04310 25 | 0.64253 0.04601 26 | 0.61713 0.04886 27 | 0.59152 0.05161 28 | 0.56575 0.05423 29 | 0.53988 0.05671 30 | 0.51400 0.05905 31 | 0.48818 0.06120 32 | 0.46248 0.06315 33 | 0.43694 0.06488 34 | 0.41165 0.06640 35 | 0.38669 0.06767 36 | 0.36210 0.06867 37 | 0.33792 0.06939 38 | 0.31423 0.06985 39 | 0.29111 0.07002 40 | 0.26858 0.06986 41 | 0.24668 0.06940 42 | 0.22548 0.06865 43 | 0.20504 0.06758 44 | 0.18539 0.06617 45 | 0.16655 0.06445 46 | 0.14856 0.06243 47 | 0.13150 0.06012 48 | 0.11537 0.05748 49 | 0.10018 0.05456 50 | 0.08598 0.05138 51 | 0.07283 0.04794 52 | 0.06072 0.04423 53 | 0.04964 0.04028 54 | 0.03961 0.03615 55 | 0.03071 0.03186 56 | 0.02292 0.02739 57 | 0.01620 0.02279 58 | 0.01057 0.01814 59 | 0.00613 0.01352 60 | 0.00288 0.00888 61 | 0.00077 0.00428 62 | 0.00000 0.00000 63 | 0.00077 -0.00428 64 | 0.00288 -0.00888 65 | 0.00613 -0.01352 66 | 0.01057 -0.01814 67 | 0.01620 -0.02279 68 | 0.02292 -0.02739 69 | 0.03071 -0.03186 70 | 0.03961 -0.03615 71 | 0.04964 -0.04028 72 | 0.06072 -0.04423 73 | 0.07283 -0.04794 74 | 0.08598 -0.05138 75 | 0.10018 -0.05456 76 | 0.11537 -0.05748 77 | 0.13150 -0.06012 78 | 0.14856 -0.06243 79 | 0.16655 -0.06445 80 | 0.18539 -0.06617 81 | 0.20504 -0.06758 82 | 0.22548 -0.06865 83 | 0.24668 -0.06940 84 | 0.26858 -0.06986 85 | 0.29111 -0.07002 86 | 0.31423 -0.06985 87 | 0.33792 -0.06939 88 | 0.36210 -0.06867 89 | 0.38669 -0.06767 90 | 0.41165 -0.06640 91 | 0.43694 -0.06488 92 | 0.46248 -0.06315 93 | 0.48818 -0.06120 94 | 0.51400 -0.05905 95 | 0.53988 -0.05671 96 | 0.56575 -0.05423 97 | 0.59152 -0.05161 98 | 0.61713 -0.04886 99 | 0.64253 -0.04601 100 | 0.66764 -0.04310 101 | 0.69236 -0.04015 102 | 0.71664 -0.03717 103 | 0.74042 -0.03418 104 | 0.76361 -0.03123 105 | 0.78613 -0.02833 106 | 0.80791 -0.02550 107 | 0.82890 -0.02276 108 | 0.84901 -0.02015 109 | 0.86817 -0.01770 110 | 0.88626 -0.01543 111 | 0.90320 -0.01332 112 | 0.91891 -0.01137 113 | 0.93335 -0.00955 114 | 0.94646 -0.00786 115 | 0.95817 -0.00629 116 | 0.96848 -0.00476 117 | 0.97745 -0.00329 118 | 0.98510 -0.00198 119 | 0.99135 -0.00097 120 | 0.99604 -0.00033 121 | 0.99899 -0.00005 122 | 1.00000 0.00000 123 | -------------------------------------------------------------------------------- /coord/s8035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swarfer/winggcode/53f20cd1f657c92b404d465e06e4f9346fc47140/coord/s8035.png -------------------------------------------------------------------------------- /f18.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swarfer/winggcode/53f20cd1f657c92b404d465e06e4f9346fc47140/f18.ico -------------------------------------------------------------------------------- /wing-2.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # python wing.pyw 4 | # Version self.Id: wing.pyw 1.1 2017/10/23 07:46:09 david Exp self. 5 | # Dec 4 2007 6 | # XYUV wing G-Code Generator for EMC2 7 | # also YZ/XZ for GRBL for straight wings only 8 | """ 9 | Copyright (C) <2008> 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | e-mail me any suggestions to "jet1024 at semo dot net" 25 | If you make money using this software 26 | you must donate self.20 USD to a local food bank 27 | or the food police will get you! Think of others from time to time... 28 | To make it a menu item in Ubuntu use the Alacarte Menu Editor and add 29 | the command python YourPathToThisFile/face.py 30 | make sure you have made the file execuatble by right 31 | clicking and selecting properties then Permissions and Execute 32 | To use with EMC2 see the instructions at: 33 | http:#wiki.linuxcnc.org/cgi-bin/emcinfo.pl?Simple_EMC_G-Code_Generators 34 | 35 | 2008-02-24 Rick Calder "rick at llamatrails dot com" 36 | Added option/code to select X0-Y0 position: Left-Rear or Left-Front 37 | To change the default, change line 171: 4=Left-Rear, 5=Left-Front 38 | 39 | 2010-01-06 Brad Hanken "chembal at gmail dot com" 40 | Added option and code to change the lead in and lead out amount 41 | If nothing is entered, the old calculated value of tool radius + .1 is still used 42 | 43 | 2014-11-00 swarfer: made metric the default 44 | add option to cut unidirectional 45 | 46 | 2017-11-16 swarfer: modified into a wing cutter, based on gwing.php by the swarfer 47 | python 2.x only... 48 | 49 | 2018-02-06 swarfer: cut wings in YZ or XZ only, straight wings on a gantry XYZ machine 50 | 2020-05-10 swarfer: add XYUZ output for 4axis GRBL from rckeith 51 | """ 52 | 53 | from Tkinter import * 54 | from tkFileDialog import * 55 | from math import * 56 | from SimpleDialog import * 57 | import ConfigParser 58 | from decimal import * 59 | import tkMessageBox 60 | import os 61 | import glob 62 | import string 63 | import re 64 | 65 | IN_AXIS = os.environ.has_key("AXIS_PROGRESS_BAR") 66 | 67 | class Application(Frame): 68 | def __init__(self, master=None): 69 | Frame.__init__(self, master, width=700, height=400, bd=1) 70 | self.grid() 71 | self.createMenu() 72 | if IN_AXIS: 73 | self.ext = '.ngc' 74 | else: 75 | self.ext = '.nc' 76 | 77 | self.inifile = os.path.join('.','wing.ini') 78 | try: 79 | self.DatDir = self.GetIniData(self.inifile,'Directories','DatFiles') 80 | except: 81 | # does not exist so write a default value 82 | self.DatDir = os.path.join('.','coord') 83 | self.WriteIniData(self.inifile,'Directories','DatFiles',self.DatDir) 84 | 85 | try: 86 | """ save in ini in the NC folder """ 87 | self.NcFileDirectory = self.GetIniData(self.inifile,'Directories','NcFiles') 88 | except: 89 | tkMessageBox.showinfo('Missing INI Data', 'You must set the\n' \ 90 | 'NC File Directory\n' \ 91 | 'before saving a file.\n' \ 92 | 'Go to Edit/NC Directory\n' \ 93 | 'in the menu to set this option') 94 | return 95 | 96 | self.createWidgets() 97 | 98 | #center the window 99 | self.update_idletasks() 100 | width = self.winfo_width() 101 | frm_width = self.winfo_rootx() - self.winfo_x() 102 | win_width = width + 2 * frm_width 103 | height = self.winfo_height() 104 | titlebar_height = self.winfo_rooty() - self.winfo_y() 105 | win_height = height + titlebar_height + frm_width 106 | x = self.winfo_screenwidth() # 2 - win_width # 2 107 | y = self.winfo_screenheight() # 2 - win_height # 2 108 | #self.At(1060,200) 109 | #self.deiconify() 110 | try: #try to read the last model saved 111 | modelname = self.GetIniData(self.inifile,'autoload','model') 112 | filename = os.path.join(self.NcFileDirectory, modelname + '.ini') 113 | if os.path.exists(filename): 114 | self.ReadModel(filename) 115 | except: 116 | pass 117 | 118 | def createMenu(self): 119 | #Create the Menu base 120 | self.menu = Menu(self) 121 | #Add the Menu 122 | self.master.config(menu=self.menu) 123 | #Create our File menu 124 | self.FileMenu = Menu(self.menu) 125 | #Add our Menu to the Base Menu 126 | self.menu.add_cascade(label='File', menu=self.FileMenu) 127 | #Add items to the menu 128 | self.FileMenu.add_command(label='New', command=self.Simple) 129 | self.FileMenu.add_command(label='Open', command=self.Simple) 130 | self.FileMenu.add_separator() 131 | self.FileMenu.add_command(label='Quit', command=self.quit) 132 | 133 | self.EditMenu = Menu(self.menu) 134 | self.menu.add_cascade(label='Edit', menu=self.EditMenu) 135 | self.EditMenu.add_command(label='Copy', command=self.CopyClpBd) 136 | self.EditMenu.add_command(label='Select All', command=self.SelectAllText) 137 | self.EditMenu.add_command(label='Delete All', command=self.ClearTextBox) 138 | self.EditMenu.add_separator() 139 | self.EditMenu.add_command(label='Preferences', command=self.Simple) 140 | self.EditMenu.add_command(label='NC Directory', command=self.NcFileDirectory) 141 | self.EditMenu.add_command(label='DAT Directory', command=self.DatFileDirectory) 142 | 143 | self.HelpMenu = Menu(self.menu) 144 | self.menu.add_cascade(label='Help', menu=self.HelpMenu) 145 | self.HelpMenu.add_command(label='Help Info', command=self.HelpInfo) 146 | self.HelpMenu.add_command(label='About', command=self.HelpAbout) 147 | 148 | def createWidgets(self): 149 | self.sp1 = Label(self) 150 | self.sp1.grid(row=0) 151 | 152 | self.st1 = Label(self, text='Model Name ') 153 | self.st1.grid(row=1, column=0, sticky=E) 154 | self.ModelNameVar = StringVar() 155 | self.ModelNameVar.set('default') 156 | self.ModelName = Entry(self, width=20, textvariable=self.ModelNameVar) 157 | self.ModelName.grid(row=1, column=1, sticky=W) 158 | self.ModelName.focus_set() 159 | 160 | self.SaveModelButton = Button(self, text='Save Model',command=self.SaveModel) 161 | self.SaveModelButton.grid(row=1, column=2) 162 | 163 | self.LoadModelButton = Button(self, text='Load Model',command=self.LoadModel) 164 | self.LoadModelButton.grid(row=1, column=3) 165 | 166 | 167 | self.st2 = Label(self, text='WingSpan ') 168 | self.st2.grid(row=2, column=0, sticky=E) 169 | self.WingSpanVar = StringVar() 170 | self.WingSpanVar.set('500') 171 | self.WingSpan = Entry(self, width=10, textvariable=self.WingSpanVar) 172 | self.WingSpan.grid(row=2, column=1, sticky=W) 173 | 174 | self.st22 = Label(self, text='Washout ') 175 | self.st22.grid(row=2, column=2, sticky=E) 176 | self.WashoutVar = StringVar() 177 | self.WashoutVar.set('0') 178 | self.Washout = Entry(self, width=10, textvariable=self.WashoutVar) 179 | self.Washout.grid(row=2, column=3, sticky=W) 180 | 181 | 182 | self.st3 = Label(self, text='Root Chord ') 183 | self.st3.grid(row=3, column=0, sticky=E) 184 | self.RootChordVar = StringVar() 185 | self.RootChordVar.set('200') 186 | self.RootChord = Entry(self, width=10, textvariable=self.RootChordVar) 187 | self.RootChord.grid(row=3, column=1, sticky=W) 188 | 189 | self.st4 = Label(self, text='Tip Chord ') 190 | self.st4.grid(row=3, column=2, sticky=E) 191 | self.TipChordVar = StringVar() 192 | self.TipChordVar.set('190') 193 | self.TipChord = Entry(self, width=10, textvariable=self.TipChordVar) 194 | self.TipChord.grid(row=3, column=3, sticky=W) 195 | 196 | 197 | self.profiles = glob.glob(os.path.join(self.DatDir, '*.dat')) 198 | self.profiles.sort() 199 | 200 | self.st5 = Label(self, text='Root Profile ') 201 | self.st5.grid(row=4, column=0, sticky=E) 202 | self.rootScrollbar = Scrollbar(self, orient=VERTICAL) 203 | self.RootProfilelistbox = Listbox(self, exportselection=0, height=5, width=18, yscrollcommand=self.rootScrollbar.set) 204 | self.rootScrollbar.config(command=self.RootProfilelistbox.yview) 205 | self.rootScrollbar.grid(row=4,column=1, sticky=N+S+E) 206 | self.RootProfilelistbox.grid(row=4, column=1, sticky=W) 207 | 208 | for item in self.profiles: 209 | nitem = string.replace(item,self.DatDir + os.sep,'') 210 | self.RootProfilelistbox.insert(END, nitem) 211 | if (len(self.profiles) > 0): 212 | self.RootProfilelistbox.selection_set(0) 213 | 214 | # self.st6 = Label(self, text='Tip Profile ') 215 | # self.st6.grid(row=4, column=2, sticky=E) 216 | # self.TipProfileVar = StringVar() 217 | # self.TipProfile = Entry(self, width=10, textvariable=self.TipProfileVar) 218 | # self.TipProfile.grid(row=4, column=3, sticky=W) 219 | 220 | self.st6 = Label(self, text='Tip Profile ') 221 | self.st6.grid(row=4, column=2, sticky=E) 222 | self.tipScrollbar = Scrollbar(self, orient=VERTICAL) 223 | self.TipProfilelistbox = Listbox(self, exportselection=0, height=5, yscrollcommand=self.tipScrollbar.set) 224 | self.tipScrollbar.config(command=self.TipProfilelistbox.yview) 225 | self.tipScrollbar.grid(row=4,column=4, sticky=N+S+W) 226 | self.TipProfilelistbox.grid(row=4, column=3, sticky=W) 227 | 228 | for item in self.profiles: 229 | nitem = string.replace(item,self.DatDir + os.sep,'') 230 | self.TipProfilelistbox.insert(END, nitem) 231 | if (len(self.profiles) > 0): 232 | self.TipProfilelistbox.selection_set(0) 233 | 234 | self.st7 = Label(self, text='Foam Chord ') 235 | self.st7.grid(row=5, column=0, sticky=E) 236 | self.FoamChordVar = StringVar() 237 | self.FoamChordVar.set(220) 238 | self.FoamChord = Entry(self, width=10, textvariable=self.FoamChordVar) 239 | self.FoamChord.grid(row=5, column=1, sticky=W) 240 | 241 | self.st8 = Label(self, text='Foam Thickness ') 242 | self.st8.grid(row=5, column=2, sticky=E) 243 | self.FoamThicknessVar = StringVar() 244 | self.FoamThicknessVar.set(50) 245 | self.FoamThickness = Entry(self, width=10, textvariable=self.FoamThicknessVar) 246 | self.FoamThickness.grid(row=5, column=3, sticky=W) 247 | 248 | 249 | self.st9 = Label(self, text='Trailing Edge Limit ') 250 | self.st9.grid(row=6, column=0, sticky=E) 251 | self.TrailingEdgeLimitVar = StringVar() 252 | self.TrailingEdgeLimitVar.set(3) 253 | self.TrailingEdgeLimit = Entry(self, width=10, textvariable=self.TrailingEdgeLimitVar) 254 | self.TrailingEdgeLimit.grid(row=6, column=1, sticky=W) 255 | 256 | self.st10 = Label(self, text='Leading Edge Sweep ') 257 | self.st10.grid(row=6, column=2, sticky=E) 258 | self.LeadingEdgeSweepVar = StringVar() 259 | self.LeadingEdgeSweepVar.set(5) 260 | self.LeadingEdgeSweep = Entry(self, width=10, textvariable=self.LeadingEdgeSweepVar) 261 | self.LeadingEdgeSweep.grid(row=6, column=3, sticky=W) 262 | 263 | 264 | self.st11 = Label(self, text='Gantry Length ') 265 | self.st11.grid(row=7, column=0, sticky=E) 266 | self.GantryLengthVar = StringVar() 267 | self.GantryLengthVar.set(600) 268 | self.GantryLength = Entry(self, width=10, textvariable=self.GantryLengthVar) 269 | self.GantryLength.grid(row=7, column=1, sticky=W) 270 | 271 | self.st12 = Label(self, text='Feedrate ') 272 | self.st12.grid(row=7, column=2, sticky=E) 273 | self.FeedrateVar = StringVar() 274 | self.FeedrateVar.set(50) 275 | self.Feedrate = Entry(self, width=10, textvariable=self.FeedrateVar) 276 | self.Feedrate.grid(row=7, column=3, sticky=W) 277 | 278 | #these two need to be radio boxes 279 | 280 | # Units Radiobutton Callback # 5 281 | # def radCallUnit(): 282 | # radSel=radVar.get() 283 | # if radSel == 1: win.configure(background=COLOR1) 284 | # elif radSel == 2: win.configure(background=COLOR2) 285 | # elif radSel == 3: win.configure(background=COLOR3) 286 | 287 | # create two Radiobuttons 288 | self.XYsideVar = IntVar() # 0 for XY right, 1 for XY left 289 | self.st13 = Label(self, text='XY side ') 290 | self.st13.grid(row=8, column=0, sticky=E) 291 | urad1 = Radiobutton(self, text='Right', variable=self.XYsideVar, value=0) 292 | urad1.grid(row=8, column=1, sticky=E) 293 | urad2 = Radiobutton(self, text='Left', variable=self.XYsideVar, value=1) 294 | urad2.grid(row=8, column=1, sticky=W) 295 | self.XYsideVar.set(0) 296 | 297 | self.st14=Label(self,text='Units : ') 298 | self.st14.grid(row=8,column=2) 299 | UnitOptions=[('Inch',1,'E'),('MM',0,'W')] 300 | self.UnitVar = IntVar() 301 | for text, value, side in UnitOptions: 302 | Radiobutton(self, text=text,value=value, variable=self.UnitVar,indicatoron=0,width=6,).grid(row=8, column=3,sticky=side) 303 | self.UnitVar.set(0) 304 | # XYUV/YZ/XZ 305 | self.XYUVVar = IntVar() # 0 for XYUV , 1 for YZ, 2 for XZ, 3 for XYUZ (GRBL mode) 306 | self.st13 = Label(self, text='XYUV/YZ/XZ/XYUZ ') 307 | self.st13.grid(row=9, column=0, sticky=E) 308 | urad1 = Radiobutton(self, text='XYUV', variable=self.XYUVVar, value=0) 309 | urad1.grid(row=9, column=1, sticky=W) 310 | urad2 = Radiobutton(self, text='YZ only', variable=self.XYUVVar, value=1) 311 | urad2.grid(row=9, column=2, sticky=W) 312 | urad1 = Radiobutton(self, text='XZ only', variable=self.XYUVVar, value=2) 313 | urad1.grid(row=9, column=3, sticky=W) 314 | urad3 = Radiobutton(self, text='XYUZ(GRBL)', variable=self.XYUVVar, value=3) 315 | urad3.grid(row=9, column=4, sticky=W) 316 | self.XYUVVar.set(0) 317 | 318 | self.spacer3 = Label(self, text='') 319 | self.spacer3.grid(row=10, column=0, columnspan=5) 320 | # gcode block 321 | self.g_code = Text(self,width=30,height=14,bd=3) 322 | self.g_code.grid(row=11, column=0, columnspan=4, sticky=E+W+N+S) 323 | self.tbscroll = Scrollbar(self,command = self.g_code.yview) 324 | self.tbscroll.grid(row=11, column=4, sticky=N+S+W) 325 | self.g_code.configure(yscrollcommand = self.tbscroll.set) 326 | 327 | self.sp4 = Label(self) 328 | self.sp4.grid(row=12) 329 | 330 | #make sure these exist so pressing save button before generate does not crash 331 | self.g_code_left = list() 332 | self.g_code_right = list() 333 | self.g_code_both = list() 334 | 335 | self.GenButton = Button(self, text='Generate G-Code',command=self.GenCode) 336 | self.GenButton.grid(row=12, column=0) 337 | 338 | # self.GenButton = Button(self, text='Generate G-Code bi',command=self.GenCode2) 339 | # self.GenButton.grid(row=12, column=1) 340 | 341 | # self.CopyButton = Button(self, text='Select All & Copy',command=self.SelectCopy) 342 | # self.CopyButton.grid(row=12, column=2) 343 | 344 | self.WriteButton = Button(self, text='Write to Files',command=self.WriteToFile) 345 | self.WriteButton.grid(row=12, column=1) 346 | 347 | if IN_AXIS: 348 | self.quitButton1 = Button(self, text='Write LEFT to AXIS and Quit', command=self.WriteLeftToAxis) 349 | self.quitButton1.grid(row=12, column=2, sticky=E) 350 | self.quitButton2 = Button(self, text='Write RIGHT to AXIS and Quit', command=self.WriteRightToAxis) 351 | self.quitButton2.grid(row=12, column=3, sticky=E) 352 | self.quitButton3 = Button(self, text='Write BOTH to AXIS and Quit', command=self.WriteBothToAxis) 353 | self.quitButton3.grid(row=12, column=4, sticky=E) 354 | else: 355 | self.quitButton = Button(self, text='Quit', command=self.MyQuit) 356 | self.quitButton.grid(row=12, column=4, sticky=E) 357 | 358 | def MyQuit(self): 359 | sys.stdout.write('%') 360 | self.quit() 361 | 362 | #python makes number formatting so hard.... 363 | def Format(self,val,dec): 364 | s = '%0.' + str(int(dec)) + 'f' 365 | return s % val 366 | 367 | #get all the values from the widgets 368 | def GetWValues(self): 369 | self.modelname = self.ModelNameVar.get() 370 | self.wingspan = self.FToD(self.WingSpanVar.get()) 371 | self.washout = self.FToD(self.WashoutVar.get()) 372 | self.rootchord = self.FToD(self.RootChordVar.get()) 373 | self.tipchord = self.FToD(self.TipChordVar.get()) 374 | 375 | items = self.RootProfilelistbox.curselection() 376 | self.rootfile = self.RootProfilelistbox.get(items[0]) 377 | #do not use ACTIVE for this 378 | items = self.TipProfilelistbox.curselection() 379 | self.tipfile = self.TipProfilelistbox.get(items[0]) 380 | 381 | self.foamchord = self.FToD(self.FoamChordVar.get()) 382 | self.foamthickness = self.FToD(self.FoamThicknessVar.get()) 383 | self.trail = self.FToD(self.TrailingEdgeLimitVar.get()) 384 | self.sweep = self.FToD(self.LeadingEdgeSweepVar.get()) 385 | self.gantry = self.FToD(self.GantryLengthVar.get()) 386 | self.feedrate = self.FToD(self.FeedrateVar.get()) 387 | if self.feedrate == 0: 388 | self.feedrate = 1 389 | self.g_code.insert(END,'WARNING: feedrate cannot be ZERO\n') 390 | self.xy = self.XYsideVar.get() 391 | self.xyuv = self.XYUVVar.get() 392 | if self.xyuv == 3: 393 | self.xyuv = 0 394 | self.grblmode = True 395 | else: 396 | self.grblmode = False 397 | self.unit = self.UnitVar.get() 398 | if self.unit: 399 | self.units = '"' 400 | else: 401 | self.units = 'mm' 402 | 403 | 404 | def Header(self,alist): 405 | alist.append('%\n') 406 | line = 'G90 ' 407 | if self.UnitVar.get()==1: 408 | line = line + 'G20 ' 409 | dec = 3 410 | self.Safe = 0.1 # 0.1 inch 411 | else: 412 | line = line + 'G21 ' 413 | self.Safe = 1 #1 mm 414 | dec = 1 415 | line = line + 'M3 S100 ' 416 | if len(self.FeedrateVar.get())>0: 417 | line = line + 'F%s\n' % self.FeedrateVar.get() 418 | else: 419 | line = line + '\n' 420 | alist.append(line) 421 | 422 | #these tell LinuxCNC where to put the XY and UV on the screen 423 | line = "(AXIS,XY_Z_POS,0)\n" 424 | alist.append(line) 425 | line = "(AXIS,UV_Z_POS,%.3f)\n" % (self.gantry) 426 | alist.append(line) 427 | 428 | alist.append('(modelname ' + self.modelname + ')\n') 429 | 430 | alist.append('(wingspan ' + self.Format(self.wingspan,dec) + ')\n') 431 | alist.append('(rootchord ' + self.Format(self.rootchord,dec) + ')\n') 432 | alist.append('(tipchord ' + self.Format(self.tipchord,dec) + ')\n') 433 | alist.append('(rootfile ' + self.rootfile + ')\n') 434 | alist.append('(tipfile ' + self.tipfile + ')\n') 435 | alist.append('(foamchord ' + self.Format(self.foamchord,dec) + ')\n') 436 | alist.append('(foamthickness ' + self.Format(self.foamthickness,dec) + ')\n') 437 | alist.append('(trail ' + self.Format(self.trail,dec) + ')\n') 438 | alist.append('(sweep ' + self.Format(self.sweep,dec) + ')\n') 439 | alist.append('(washout ' + self.Format(self.washout,dec) + ')\n') 440 | alist.append('(gantry ' + self.Format(self.gantry,dec) + ')\n') 441 | alist.append('(feedrate ' + self.Format(self.feedrate,dec) + ')\n') 442 | if (self.xy == 0): 443 | alist.append('(xy right)\n') 444 | else: 445 | alist.append('(xy left)\n') 446 | if (self.unit == 0): 447 | alist.append('(unit MM)\n') 448 | else: 449 | alist.append('(unit inch)\n') 450 | alist.append('(NOTE 0,0 is wire on table at front foam corner)\n') 451 | 452 | 453 | def GenCode(self): 454 | """ will generate all three gcode files as string lists """ 455 | self.g_code_left = [] 456 | self.g_code_right = [] 457 | self.g_code_both = [] 458 | self.g_code.delete("1.0",END) 459 | self.GetWValues() 460 | if (self.wingspan >= self.gantry): 461 | self.g_code.insert(END,"PANIC: wingspan greater than gantry separation\n") 462 | return None 463 | #print "sweep %f" % self.sweep 464 | self.gap = (self.gantry - self.wingspan) / 2 # gap between end of wing and gantry on each side 465 | self.teoff = self.rootchord - self.sweep - self.tipchord # opposite of sweep 466 | if (self.teoff < 0): 467 | self.g_code.insert(END, " Swept Wing\n") 468 | #print "teoff %f" %self.teoff 469 | 470 | self.t1 = atan(self.sweep / self.wingspan) 471 | self.t2 = atan(self.teoff / self.wingspan) 472 | self.E1 = tan(self.t1) * self.gap 473 | self.E2 = tan(self.t2) * self.gap 474 | self.E3 = tan(self.t1) * (self.wingspan + self.gap) 475 | self.E4 = tan(self.t2) * (self.wingspan + self.gap) 476 | self.debug = 0 477 | if self.debug: 478 | print("E1 %0.4f" % self.E1) 479 | print("E2 %0.4f" % self.E2) 480 | print("E3 %0.4f" % self.E3) 481 | print("E4 %0.4f" % self.E4) 482 | 483 | self.rootlength = self.rootchord + self.E1 + self.E2 # gantry movement for root 484 | if (self.teoff < 0): 485 | self.waste = self.E1 # # wastage at trailing edge of tip profile 486 | else: 487 | self.waste = self.E2 # # wastage at trailing edge of root profile 488 | self.tiplength = self.rootchord - self.E3 - self.E4 # gantry movement for tip 489 | 490 | if (self.debug): 491 | print("rootlength %0.4f" % self.rootlength) 492 | print("tiplength %0.4f" % self.tiplength) 493 | 494 | if (self.tiplength < 0): 495 | self.g_code.insert(END, "\n PANIC: tip length is negative, I cannot plot this,\n you need to put the gantry closer together\n") 496 | return None 497 | 498 | self.toffset = self.E2 + self.E4 # tip offset for tip gantry 499 | if (self.debug): 500 | print "toffset %0.4f" % self.toffset 501 | 502 | self.g_code.insert(END, "rootfile " + self.rootfile + '\n') 503 | self.g_code.insert(END, "tipfile " + self.tipfile + '\n') 504 | 505 | #root file 506 | if not(os.path.exists(self.rootfile)): 507 | self.rootfile = os.path.join(self.DatDir, self.rootfile) 508 | #tip 509 | if not(os.path.exists(self.tipfile)): 510 | self.tipfile = os.path.join(self.DatDir,self.tipfile) 511 | if (not(os.path.exists(self.rootfile)) or not(os.path.exists(self.tipfile))): 512 | self.g_Code.insert(END, " PANIC: either root or tip file not found\n") 513 | return None 514 | 515 | 516 | # trailing edge thickness limit, does not work if number of points is low 517 | if (self.trail > 0): 518 | self.g_code.insert(END," Trailing edge " + str(self.trail) + '\n' ) 519 | 520 | # foam management 521 | self.skintop = 0 522 | self.skinbot = 0 523 | 524 | # assume start at trailing edge on root and tip+self.toffset profile, wire on platten at 0,0 525 | # then feed top surface 526 | # then bottom 527 | # then feed out 528 | 529 | with open(self.rootfile) as f: 530 | self.rootprofile = f.read().splitlines() 531 | with open(self.tipfile) as f: 532 | self.tipprofile = f.read().splitlines() 533 | 534 | self.rootprofile = self.stripfile(self.rootprofile) 535 | self.tipprofile = self.stripfile(self.tipprofile) # get rid of comment lines 536 | 537 | if (len(self.rootprofile) != len(self.tipprofile)): 538 | #self.g_code.insert(END," ERROR profile point counts not equal (%d,%d)\n" % (len(self.rootprofile) , len(self.tipprofile))) 539 | #return None 540 | if (len(self.rootprofile) > len(self.tipprofile)): 541 | self.g_code.insert(END, "root bigger, resampling tip") 542 | self.tipprofile = self.resample(self.rootprofile, self.tipprofile) 543 | else: 544 | self.g_code.insert(END,"tip bigger, resmapling root") 545 | self.rootprofile = self.resample(self.tipprofile, self.rootprofile) 546 | 547 | self.FindThicknessesRoot() 548 | if self.debug: print("Actualrootthick " + str(self.actualrootthick)) 549 | self.CreateTip() 550 | if self.washout != 0.0: 551 | self.tip = self.rotatePolygon(self.tip, -self.washout) # rotate nose down, keep trailing edge straight 552 | self.FindThicknessesTip() 553 | 554 | self.TrailingEdgeLimits2() 555 | self.FindStartPoints() 556 | 557 | # info 558 | self.g_code.insert(END, " startpoint sp= %0.3f%s above platten\n" % (self.sp, self.units) ) 559 | if self.need == 0: 560 | self.g_code.insert(END, " startpoint spL= %0.3f%s above platten\n" % (self.spl, self.units)) 561 | self.g_code.insert(END, " startpoint spR= %0.3f%s above platten\n" % (self.spr, self.units)) 562 | self.l = self.rootchord + self.waste 563 | if (self.debug): 564 | print("L %.2f waste %.2f" % (self.l,self.waste)) 565 | if (self.l > self.foamchord): 566 | self.g_code.insert(END, "WARNING: root length + waste(%.2f) is greater than the foam chord(%.2f) you specified\n" % (self.l, self.foamchord)) 567 | 568 | # Generate the G-Codes 569 | self.Header(self.g_code_left) 570 | self.Header(self.g_code_right) 571 | self.Header(self.g_code_both) 572 | 573 | if self.xy: 574 | self.g_code.insert(END, 'XY gantry left\n') 575 | else: 576 | self.g_code.insert(END, 'XY gantry right\n') 577 | # os.path.join(self.NcFileDirectory, self.modelname, '-right' + self.ext) 578 | # os.path.join(self.NcFileDirectory, self.modelname, '-left' + self.nc) 579 | # os.path.join(self.NcFileDirectory, self.modelname,'-both' + self.nc) 580 | 581 | if (self.xy): #XY on left 582 | self.plot(self.root, self.tip, 'right', self.g_code_right, self.sp, 1, 0) 583 | self.plot(self.tip, self.root,'left' , self.g_code_left, self.sp, 1, 0) 584 | if (self.need == 0): 585 | self.g_code.insert(END, "Doing BOTH file\n") 586 | self.plot(self.root, self.tip, 'right', self.g_code_both, self.spl, 1, 1) 587 | self.plot(self.root, self.tip, 'right' , self.g_code_both, self.spr, -1, 1) 588 | else: 589 | self.g_code.insert(END, "Did not output BOTH file since foam is not thick enough\n") 590 | else: #XY on right 591 | self.plot(self.root, self.tip, 'left', self.g_code_left, self.sp, 1, 0) 592 | self.plot(self.tip, self.root,'right' , self.g_code_right, self.sp, 1, 0) 593 | 594 | # output combined foils for GRBL XYUV 595 | if (self.need == 0): 596 | self.g_code.insert(END, "Doing BOTH file\n") 597 | self.plot(self.root, self.tip, 'left', self.g_code_both, self.spl, 1, 1) 598 | self.plot(self.root, self.tip, 'left' , self.g_code_both, self.spr, -1, 1) 599 | else: 600 | self.g_code.insert(END, "Did not output BOTH file since foam is not thick enough\n") 601 | 602 | 603 | #for line in self.g_code_left: 604 | # self.g_code.insert(END, line) 605 | """ 606 | p1 first airfoil data 607 | p2 seconds airfoil data 608 | direc 'left' or 'right' for direction 609 | fname the filename 610 | sp start point , distance above X Zero 611 | invert 1 for normal, -1 for invert 612 | flag 0 for normal, 1 for BOTH mode 613 | """ 614 | def plot(self, p1, p2, direc, flist, sp, invert, flag): 615 | #global $inch, $units, $trail, $E2, $wingspan, $params, $rootymax, $rootymin, $tipymax, $tipymin, self.toffset, $feedspeed, $rootlength, $tiplength, $waste, $foamthick, $foamchord, $skintop, $skinbot, $xy; 616 | # if (flag): 617 | # if (invert == 1): 618 | # flist.append("%%\n") 619 | # else: 620 | # flist.append( "%%\n"); 621 | flist.append( "(Generated by wing.py. David the Swarfer, 2017)\n") 622 | flist.append( "(from ini file root " + os.path.basename(self.rootfile) + " tip " + os.path.basename(self.tipfile) + " offset %.2f )\n" % (self.toffset)) 623 | if (self.toffset > 0): 624 | flist.append( "(minimum material chord is %0.1f with wastage of %.3f at trailing edge)\n" % (self.rootlength, self.waste)) 625 | else: 626 | mmc = self.rootchord + self.waste 627 | w = -self.E2 628 | flist.append( "(Swept Wing)\n") 629 | flist.append( "(minimum material chord is %0.3f%s with wastage of %0.3f%s at trailing edge)\n" % (mmc, self.units,w,self.units)) 630 | if (self.xyuv != 0): 631 | flist.append("(generating cartesian gantry, taper ignored)\n") 632 | 633 | flist.append( "(root gantry thickness = %0.1f%s)\n" % (self.rootymax - self.rootymin, self.units)) 634 | flist.append( "(tip gantry thickness = %0.1f%s)\n" % (self.tipymax - self.tipymin, self.units)) 635 | if (self.rootlength != self.tiplength): 636 | flist.append( "(above sizes are for gantry travel, actual wing will be thinner)\n") 637 | if (self.foamchord and self.foamthickness): 638 | ws = self.wingspan 639 | flist.append( "(foam block %.3f'wingspan' x %.3f x %.3f%s)\n" % (ws,self.foamchord,self.foamthickness,self.units)); 640 | if (self.xyuv == 0): 641 | if (self.xy): 642 | flist.append( "(XY gantry left)\n") 643 | else: 644 | flist.append( "(XY gantry right)\n") 645 | else: 646 | if (self.xyuv == 1): 647 | self.g_code.insert(END, "YZ") 648 | flist.append( "(YZ gantry)\n") 649 | else: 650 | self.g_code.insert(END, "XZ" ) 651 | flist.append( "(XZ gantry)\n") 652 | #z = 0 - self.rootymin 653 | flist.append( "(trailing edge will be AT LEAST %0.1f%s above bottom of panel)\n" % (self.sp, self.units)) 654 | #if (self.trail > 0): 655 | # flist.append( "(Trailing edge limit %0.2f%s)\n" % (self.trail, self.units)) 656 | if (self.unit): 657 | flist.append( "G20\n"); # inch mode 658 | prec = '%0.4f' # thous/10 for inch mode 659 | retract = -0.25 660 | else: 661 | flist.append( "G21\n") # metric mode 662 | prec = '%0.3f' # 1/1000 mm for metricmode 663 | retract = -5.0 664 | 665 | if (self.xyuv == 0): 666 | flist.append( "G90\n") 667 | if self.grblmode == False: 668 | flist.append("G49 G64 P0.01\n") 669 | else: 670 | flist.append( "G90 G49\n") 671 | 672 | 673 | if self.debug: print("prec " + str(prec)) 674 | # you will need to add in any other header codes you need, here 675 | 676 | if self.debug: 677 | print(retract) 678 | print(sp) 679 | print(self.feedrate) 680 | if (self.xyuv == 0): 681 | if self.grblmode: 682 | fmt = "G00 X"+prec+" Y"+prec+" U"+prec+" Z"+prec+" F%0.1f\n" 683 | else: 684 | fmt = "G00 X"+prec+" Y"+prec+" U"+prec+" V"+prec+" F%0.1f\n" 685 | else: 686 | if (self.xyuv == 1): # YZ 687 | fmt = "G00 Y"+prec+" Z"+prec+" F%0.1f\n" 688 | else: #XZ 689 | fmt = "G00 X"+prec+" Z"+prec+" F%0.1f\n" 690 | #start heights, sp + offset of first point on top surface 691 | if flag and (invert == -1): 692 | #doing an upside down profile, do lower side first, ie run loop backwards 693 | start = len(p1) - 1 694 | else: 695 | start = 0 696 | if (self.xy): # XY gantry on left 697 | if (direc == 'right'): # means second profile is smaller, 698 | shXY = sp + p1[start][1] 699 | shUV = sp + p2[start][1] 700 | else: 701 | shXY = sp + p2[start][1] 702 | shUV = sp + p1[start][1] 703 | else: # XY gantry on right 704 | if (direc == 'left'): # means second profile is smaller, 705 | shXY = sp + p1[start][1] 706 | shUV = sp + p2[start][1] 707 | else: 708 | shXY = sp + p2[start][1] 709 | shUV = sp + p1[start][1] 710 | flist.append("(seek to start height)\n") 711 | if (self.xyuv == 0): 712 | flist.append( fmt % (retract,shXY,retract,shUV,self.feedrate)) # EMC bleats if no feed speed on first G0 instructions 713 | else: 714 | flist.append( fmt % (retract,shXY,self.feedrate)) 715 | spt = [0,shXY,0,shUV] 716 | # do skins and then cut, assume wire 0,0 on surface of platten 717 | self.g_code.insert(END, " doing '%s' with Foamthick %.3f%s\n" % (direc,self.foamthickness, self.units)) 718 | """ 719 | if (($skintop > 0) || ($skinbot > 0) && ( ($foamthick >0) && ($foamchord > 0) )) 720 | { 721 | fprintf($of, "(skinning $skintop $skinbot on block $wingspan x $foamchord x $foamthick)\n"); 722 | $top = $foamthick - $skintop; 723 | fprintf($of,"G00 Y%0.2f V%0.2f\n",$top,$top); 724 | $fc = $foamchord + 5; 725 | fprintf($of,"G01 X%0.1f U%0.1f\n",$fc,$fc); 726 | fprintf($of,"G00 Y%0.2f V%0.2f\n",$skinbot,$skinbot); 727 | fprintf($of,"G01 X-5 U-5\n"); 728 | fprintf($of,"G00 Y%0.2f V%0.2f\n",$sp,$sp); 729 | } 730 | else 731 | fprintf($of, "(no skins)\n"); 732 | """ 733 | # seek to start point 734 | # must create the format string before using it 735 | if (self.xyuv == 0): 736 | if self.grblmode: 737 | fmt = "G01 X"+prec+" Y"+prec+" U"+prec+" Z"+prec+"\n" 738 | else: 739 | fmt = "G01 X"+prec+" Y"+prec+" U"+prec+" V"+prec+"\n" 740 | else: 741 | if (self.xyuv == 1): 742 | fmt = "G01 Y"+prec+" Z"+prec+"\n" 743 | else: 744 | fmt = "G01 X"+prec+" Z"+prec+"\n" 745 | if (self.xyuv != 0) and (self.toffset != 0): 746 | flist.append("(Need a Straight wing please)\n") 747 | self.g_code.insert(END, " Straight wings only! aborting") 748 | return 749 | #else: 750 | #flist.append("(do we need a seek to trailing edge here?)\n") 751 | 752 | if (self.toffset > 0): 753 | flist.append("(seek to trailing edge)\n") 754 | if (self.xy): # XY gantry on left 755 | if (direc == 'right'): # means second profile is smaller, 756 | spt = [0,shXY, self.toffset, shUV] 757 | flist.append( fmt % (0,shXY, self.toffset, shUV)) 758 | else: 759 | spt = [self.toffset,shXY,0,shUV] 760 | flist.append( fmt % (self.toffset,shXY,0,shUV)) # first profile smaller 761 | else: # XY gantry on right 762 | if (direc == 'left'): # means second profile is smaller, 763 | spt = [0,shXY,self.toffset,shUV] 764 | flist.append( fmt % (0,shXY,self.toffset,shUV)) 765 | else: 766 | spt = [ self.toffset,shXY,0,shUV] 767 | flist.append( fmt % (self.toffset,shXY,0,shUV)) # first profile smaller 768 | if (self.toffset < 0): 769 | flist.append("(seek to trailing edge, swept)\n" ) 770 | if (self.xy): # XY gantry on left 771 | if (direc == 'right'): # means second profile is smaller, 772 | spt = [-self.toffset, shXY,0,shUV] 773 | flist.append( fmt % (-self.toffset, shXY,0,shUV)) 774 | else: 775 | spt = [ 0,shXY,-self.toffset,shUV] 776 | flist.append( fmt % (0,shXY,-self.toffset,shUV)) # first profile smaller 777 | else: # XY gantry on right 778 | if (direc == 'left'): # means second profile is smaller, 779 | spt = [-self.toffset,shXY,0,shUV] 780 | flist.append( fmt % (-self.toffset,shXY,0,shUV)) 781 | else: 782 | spt =[0,shXY,-self.toffset,shUV] 783 | flist.append( fmt % (0,shXY,-self.toffset,shUV)) # first profile smaller 784 | 785 | #$xoffset = ($toffset < 0) ? -$toffset : 0; 786 | #$uoffset = ($toffset < 0) ? 0 : $toffset ; 787 | 788 | if (self.toffset < 0): 789 | xoffset = -self.toffset 790 | uoffset = 0 791 | else: 792 | xoffset = 0 793 | uoffset = self.toffset 794 | 795 | if flag and (invert == -1): 796 | #doing an upside down profile, do lower side first, ie run loop backwards 797 | start = len(p1) - 1 798 | end = -1 799 | step = -1 800 | else: 801 | #do top surface first as normal 802 | start = 0 803 | end = len(p1) 804 | step = 1 805 | #print "%d %d %d" %(start,end,step) 806 | #print len(p1), len(p2) 807 | flist.append("(do profile)\n" ) 808 | for idx in range(start, end, step): 809 | #print "idx %d" % idx 810 | if (self.xyuv == 0): 811 | if (self.xy): # XY gantry on left 812 | if (direc == 'right'): 813 | flist.append(fmt % (p1[idx][0] + xoffset, sp + p1[idx][1] * invert, p2[idx][0] + uoffset, sp + p2[idx][1] * invert)) 814 | else: 815 | flist.append(fmt % (p1[idx][0] + uoffset, sp + p1[idx][1] * invert, p2[idx][0] + xoffset, sp + p2[idx][1] * invert)) 816 | else: # XY gantry on right 817 | if (direc == 'left'): 818 | flist.append(fmt % (p1[idx][0] + xoffset, sp + p1[idx][1] * invert, p2[idx][0] + uoffset, sp + p2[idx][1] * invert)) 819 | else: 820 | flist.append(fmt % (p1[idx][0] + uoffset, sp + p1[idx][1] * invert, p2[idx][0] + xoffset, sp + p2[idx][1] * invert)) 821 | else: 822 | #self.g_code.insert(END, fmt) 823 | flist.append(fmt % (p1[idx][0] + xoffset, sp + p1[idx][1] * invert )) 824 | #end idx loop 825 | 826 | #close the trailing edge by going back to start point 827 | flist.append("(close trailing edge)\n" ) 828 | if (self.xyuv == 0): 829 | flist.append( fmt % (spt[0], spt[1], spt[2], spt[3]) ) 830 | else: 831 | flist.append( fmt % (spt[0], spt[1]) ) 832 | if (self.xyuv != 0): 833 | flist.append("G4 P0.075\n") 834 | if self.unit: 835 | retract = -0.25 836 | else: 837 | retract = -5 838 | #retract 839 | flist.append("(retract out of foam)\n" ) 840 | if (self.xyuv == 0): 841 | flist.append( fmt % (retract,shXY,retract,shUV)) 842 | else: 843 | flist.append( fmt % (retract*2,shXY)) 844 | flist.append("G4 P0.075\n") 845 | if ( not(flag) or ( (invert == -1) and flag)): 846 | if (self.xyuv == 0): 847 | if self.grblmode: 848 | fmt0 = "G00 X"+prec+" Y"+prec+" U"+prec+" Z"+prec+"\n" 849 | else: 850 | fmt0 = "G00 X"+prec+" Y"+prec+" U"+prec+" V"+prec+"\n" 851 | flist.append( fmt0 % (2*retract,-2*retract,2*retract,-2*retract)) 852 | else: 853 | if (self.xyuv == 1): 854 | fmt0 = "G00 Y"+prec+" Z"+prec+"\n" 855 | else: 856 | fmt0 = "G00 X"+prec+" Z"+prec+"\n" 857 | flist.append( fmt0 % (2*retract,-2*retract)) 858 | if (flag): 859 | if (invert == -1): 860 | flist.append("M5\nM30\n") 861 | flist.append("%\n") 862 | else: 863 | flist.append("M5\nM30\n") 864 | flist.append("%\n") 865 | #end of plot() 866 | 867 | def WriteLeftToAxis(self): 868 | for line in self.g_code_left: 869 | sys.stdout.write(line) 870 | self.quit() 871 | 872 | def WriteRightToAxis(self): 873 | for line in self.g_code_right: 874 | sys.stdout.write(line) 875 | self.quit() 876 | 877 | def WriteBothToAxis(self): 878 | if len(self.g_code_both) > 100: 879 | for line in self.g_code_both: 880 | sys.stdout.write(line) 881 | self.quit() 882 | else: 883 | self.g_code.insert(END,'ERROR: no BOTH data to write') 884 | 885 | def SaveModel(self): 886 | """ save an ini file like this 887 | [drunk] 888 | wingspan=770 889 | root=340 890 | tip=150 891 | trail=1 892 | sweep=50 893 | gantry=900 894 | rootfile=e374.dat 895 | tipfile=e374.dat 896 | foamchord=410 897 | foamthick=50 898 | feedspeed=345 899 | xy=0 900 | inch=0 901 | """ 902 | 903 | try: 904 | """ save in ini in the NC folder """ 905 | self.NcFileDirectory = self.GetIniData(self.inifile,'Directories','NcFiles') 906 | except: 907 | tkMessageBox.showinfo('Missing INI Data', 'You must set the\n' \ 908 | 'NC File Directory\n' \ 909 | 'before saving a file.\n' \ 910 | 'Go to Edit/NC Directory\n' \ 911 | 'in the menu to set this option') 912 | return 913 | 914 | modelname = self.ModelNameVar.get() 915 | modelname = modelname.strip() 916 | if (modelname == ''): 917 | tkMessageBox.showinfo('Need a model name in order to save a model') 918 | return 919 | 920 | inifile = self.NcFileDirectory +'/'+ modelname + '.ini' 921 | 922 | config = ConfigParser.SafeConfigParser() 923 | 924 | # When adding sections or items, add them in the reverse order of 925 | # how you want them to be displayed in the actual file. 926 | # In addition, please note that using RawConfigParser's and the raw 927 | # mode of ConfigParser's respective set functions, you can assign 928 | # non-string values to keys internally, but will receive an error 929 | # when attempting to write to a file or when you get it in non-raw 930 | # mode. SafeConfigParser does not allow such assignments to take place. 931 | config.add_section(modelname) 932 | config.set(modelname, 'wingspan', self.WingSpanVar.get()) 933 | config.set(modelname, 'washout', self.WashoutVar.get()) 934 | config.set(modelname, 'root', self.RootChordVar.get()) 935 | config.set(modelname, 'tip' , self.TipChordVar.get()) 936 | items = self.RootProfilelistbox.curselection() 937 | item = self.RootProfilelistbox.get(items[0]) 938 | config.set(modelname, 'rootfile', item) 939 | items = self.TipProfilelistbox.curselection() 940 | item = self.TipProfilelistbox.get(items[0]) 941 | config.set(modelname, 'tipfile', item) 942 | config.set(modelname, 'foamchord', self.FoamChordVar.get()) 943 | config.set(modelname, 'foamthick', self.FoamThicknessVar.get()) 944 | config.set(modelname, 'trail', self.TrailingEdgeLimitVar.get()) 945 | config.set(modelname, 'sweep', self.LeadingEdgeSweepVar.get()) 946 | config.set(modelname, 'gantry', self.GantryLengthVar.get()) 947 | config.set(modelname, 'feedspeed', self.FeedrateVar.get()) 948 | xy = self.XYsideVar.get() 949 | config.set(modelname, 'xy', str(xy)) 950 | unit = self.UnitVar.get() 951 | config.set(modelname, 'inch', str(unit)) 952 | 953 | # Writing our configuration file to 'example.cfg' 954 | with open(inifile, 'wb') as configfile: 955 | config.write(configfile) 956 | self.g_code.insert(END, 'Saved model\n') 957 | self.WriteIniData(self.inifile,'autoload','model',modelname) #write for autoload 958 | 959 | def ReadModel(self, filename): 960 | config = ConfigParser.SafeConfigParser() 961 | config.read(filename) 962 | #get the model name from the filename 963 | modelname = os.path.splitext(os.path.basename(filename))[0] 964 | if (config.has_section(modelname)): 965 | self.ModelNameVar.set(modelname) 966 | self.WingSpanVar.set( config.get(modelname, 'wingspan')) 967 | try: 968 | self.WashoutVar.set( config.get(modelname, 'washout')) 969 | except: 970 | self.WashoutVar.set('0') 971 | self.RootChordVar.set(config.get(modelname, 'root')) 972 | self.TipChordVar.set( config.get(modelname, 'tip' )) 973 | 974 | item = config.get(modelname, 'rootfile') 975 | item = os.path.join(self.DatDir, item) 976 | last = len(self.profiles) - 1 977 | self.RootProfilelistbox.selection_clear(0, last) 978 | self.RootProfilelistbox.selection_set(self.profiles.index(item)) 979 | 980 | item = config.get(modelname, 'tipfile') 981 | item = os.path.join(self.DatDir, item ) 982 | self.TipProfilelistbox.selection_clear(0, last) 983 | self.TipProfilelistbox.selection_set(self.profiles.index(item)) 984 | 985 | self.FoamChordVar.set( config.get(modelname, 'foamchord')) 986 | self.FoamThicknessVar.set( config.get(modelname, 'foamthick')) 987 | self.TrailingEdgeLimitVar.set( config.get(modelname, 'trail')) 988 | self.LeadingEdgeSweepVar.set( config.get(modelname, 'sweep')) 989 | self.GantryLengthVar.set( config.get(modelname, 'gantry')) 990 | self.FeedrateVar.set( config.get(modelname, 'feedspeed')) 991 | self.XYsideVar.set( config.getint(modelname, 'xy')) 992 | self.UnitVar.set( config.getint(modelname, 'inch')) 993 | self.g_code.insert(END, 'loaded model ' + modelname + "\n") 994 | self.modelname = modelname 995 | return 1 996 | else: 997 | return 0 998 | 999 | def LoadModel(self): 1000 | try: 1001 | """ save in ini in the NC folder """ 1002 | self.NcFileDirectory = self.GetIniData(self.inifile,'Directories','NcFiles') 1003 | except: 1004 | tkMessageBox.showinfo('Missing INI Data', 'You must set the\n' \ 1005 | 'NC File Directory\n' \ 1006 | 'before saving a file.\n' \ 1007 | 'Go to Edit/NC Directory\n' \ 1008 | 'in the menu to set this option') 1009 | return 1010 | filename = askopenfilename(initialdir=self.NcFileDirectory,defaultextension='.ini',filetypes=[('INI','*.ini')]) 1011 | if self.ReadModel(filename): 1012 | #write this modelname to the ini file for autoload at startup 1013 | self.WriteIniData(self.inifile,'autoload','model',self.modelname) 1014 | 1015 | 1016 | """ 1017 | def WriteToAxis(self): 1018 | sys.stdout.write(self.g_code.get(0.0, END)) 1019 | self.quit() 1020 | """ 1021 | 1022 | #what code is this? 1023 | def FToD(self,s): # Float To Decimal 1024 | """ 1025 | Returns a decimal with 4 place precision 1026 | valid imputs are any fraction, whole number space fraction 1027 | or decimal string. The input must be a string! 1028 | """ 1029 | s = s.strip(' ') # remove any leading and trailing spaces 1030 | if s == '': # make sure it does not crash on empty string 1031 | s = '0' 1032 | D=Decimal # Save typing 1033 | P=D('0.000001') # Set the precision wanted 1034 | if ' ' in s: # if it is a whole number with a fraction 1035 | w,f=s.split(' ',1) 1036 | w=w.strip(' ') # make sure there are no extra spaces 1037 | f=f.strip(' ') 1038 | n,d=f.split('/',1) 1039 | ret = D(D(n)/D(d)+D(w)).quantize(P) 1040 | return float(ret) 1041 | elif '/' in s: # if it is just a fraction 1042 | n,d=s.split('/',1) 1043 | ret = D(D(n)/D(d)).quantize(P) 1044 | return float(ret) 1045 | ret = D(s).quantize(P) # if it is a decimal number already 1046 | return float(ret) 1047 | 1048 | def GetIniData(self,FileName,SectionName,OptionName): 1049 | """ 1050 | Returns the data in the file, section, option if it exists 1051 | of an .ini type file created with ConfigParser.write() 1052 | If the file is not found or a section or an option is not found 1053 | returns an exception 1054 | """ 1055 | self.cp = ConfigParser.SafeConfigParser() 1056 | try: 1057 | self.cp.read(FileName) 1058 | try: 1059 | self.cp.has_section(SectionName) 1060 | try: 1061 | IniData=self.cp.get(SectionName,OptionName) 1062 | except ConfigParser.NoOptionError: 1063 | raise Exception,'NoOptionError' 1064 | except ConfigParser.NoSectionError: 1065 | raise Exception,'NoSectionError' 1066 | except IOError: 1067 | raise Exception,'NoFileError' 1068 | return IniData 1069 | 1070 | def WriteIniData(self,FileName,SectionName,OptionName,OptionData): 1071 | """ 1072 | Pass the file name, section name, option name and option data 1073 | When complete returns 'sucess' 1074 | """ 1075 | self.cp = ConfigParser.SafeConfigParser() 1076 | self.cp.read(FileName) # read existing stuff and add to it 1077 | if not self.cp.has_section(SectionName): 1078 | self.cp.add_section(SectionName) 1079 | self.cp.set(SectionName,OptionName,OptionData) 1080 | with open(FileName, 'wb') as configfile: 1081 | self.cp.write(configfile) 1082 | 1083 | 1084 | def GetDirectory(self): 1085 | self.DirName = askdirectory(initialdir='/home',title='Please select a directory') 1086 | if len(self.DirName) > 0: 1087 | return self.DirName 1088 | 1089 | def CopyClpBd(self): 1090 | self.g_code.clipboard_clear() 1091 | self.g_code.clipboard_append(self.g_code.get(0.0, END)) 1092 | 1093 | def WriteToFile(self): 1094 | try: 1095 | self.NcFileDirectory = self.GetIniData(self.inifile,'Directories','NcFiles') 1096 | except: 1097 | tkMessageBox.showinfo('Missing INI', 'You must set the\n' \ 1098 | 'NC File Directory\n' \ 1099 | 'before saving a file.\n' \ 1100 | 'Go to Edit/NC Directory\n' \ 1101 | 'in the menu to set this option') 1102 | # try: 1103 | # os.path.join(self.NcFileDirectory, self.modelname, '-right.nc') 1104 | # os.path.join(self.NcFileDirectory, self.modelname, '-left.nc') 1105 | # os.path.join(self.NcFileDirectory, self.modelname,'-both.nc') 1106 | if (self.xyuv == 0 ): 1107 | fname = os.path.join(self.NcFileDirectory, self.modelname+ '-right' + self.ext) 1108 | of = open(fname,'w') 1109 | for line in self.g_code_right: 1110 | of.write(line) 1111 | of.close() 1112 | self.g_code.insert(END, 'Right file written ' + fname + '\n') 1113 | 1114 | fname = os.path.join(self.NcFileDirectory, self.modelname + '-left' + self.ext) 1115 | of = open(fname,'w') 1116 | for line in self.g_code_left: 1117 | of.write(line) 1118 | of.close() 1119 | self.g_code.insert(END, 'Left file written ' + fname + '\n') 1120 | 1121 | both = os.path.join(self.NcFileDirectory, self.modelname + '-both' + self.ext) 1122 | if self.need == 0: 1123 | of = open(both,'w') 1124 | for line in self.g_code_both: 1125 | of.write(line) 1126 | of.close() 1127 | self.g_code.insert(END, 'Both file written ' + both + '\n') 1128 | else: 1129 | if os.path.exists(both): 1130 | os.unlink(both) 1131 | else: # write one file for cartesian cutter 1132 | if (self.xyuv == 1 ): 1133 | tag = 'YZ' 1134 | else: 1135 | tag = 'XZ' 1136 | fname = os.path.join(self.NcFileDirectory, self.modelname + '-'+tag + self.ext) 1137 | of = open(fname,'w') 1138 | for line in self.g_code_left: 1139 | of.write(line) 1140 | of.close() 1141 | self.g_code.insert(END, 'Cartesian written ' + fname + '\n') 1142 | 1143 | #self.NewFileName = asksaveasfile(initialdir=self.NcFileDirectory,mode='w', master=self.master,title='Create NC File',defaultextension='.ngc') 1144 | #self.NewFileName.write(self.g_code.get(0.0, END)) 1145 | #self.NewFileName.close() 1146 | # except: 1147 | # tkMessageBox.showinfo('broken','something broke while writing files\n') 1148 | 1149 | def NcFileDirectory(self): 1150 | DirName = self.GetDirectory() 1151 | if len(DirName) > 0: 1152 | self.WriteIniData(self.inifile,'Directories','NcFiles',DirName) 1153 | 1154 | # this is the folder where we find our .dat files for foil shapes 1155 | def DatFileDirectory(self): 1156 | DirName = self.GetDirectory() 1157 | if len(DirName) > 0: 1158 | self.WriteIniData(self.inifile,'Directories','DatFiles',DirName) 1159 | 1160 | def Simple(self): 1161 | tkMessageBox.showinfo('Feature', 'Sorry this Feature has\nnot been programmed yet.') 1162 | 1163 | def ClearTextBox(self): 1164 | self.g_code.delete(1.0,END) 1165 | 1166 | def SelectAllText(self): 1167 | self.g_code.tag_add(SEL, '1.0', END) 1168 | 1169 | def SelectCopy(self): 1170 | self.SelectAllText() 1171 | self.CopyClpBd() 1172 | 1173 | def HelpInfo(self): 1174 | SimpleDialog(self, 1175 | text='See the github page for help.\n', 1176 | buttons=['Ok'], 1177 | default=0, 1178 | title='User Info').go() 1179 | def HelpAbout(self): 1180 | tkMessageBox.showinfo('Help About', 'Programmed by\n' 1181 | 'the Swarfer\n' 1182 | 'Version 1.0.1') 1183 | 1184 | # take an array read from a dat file and strip out all comments so we only have the co-ord numbers on return 1185 | def stripfile(self, thefile): 1186 | done = 0 1187 | while not(done): 1188 | done = 1 1189 | bits = 0 1190 | for key in range(0, len(thefile)-1): 1191 | line = thefile[key] 1192 | bits = string.strip(line) 1193 | bits = string.split(bits) #(preg_split('/[ ]+|\t/',trim(self.line)); 1194 | for idx in range(0, len(bits)-1): # prevent losing lines like '0 0' 1195 | if (bits[idx] == '0'): 1196 | bits[idx] = '0.0' 1197 | if (len(bits) == 3) and (re.search('[a-d]|[f-z]', line) == None ): # some files have 3 fields, remove first one, the line number 1198 | bits.remove( bits[0]) 1199 | if ( re.search('[a-d]|[f-z]|[A-D]|[F-Z]',line) != None ): 1200 | thefile.remove(line) 1201 | done = 0 1202 | break 1203 | if ( line == ''): 1204 | thefile.remove(line) 1205 | done = 0 1206 | break 1207 | thefile[key] = string.strip(line) 1208 | return thefile 1209 | 1210 | #creates the self.root array of cordinates 1211 | def FindThicknessesRoot(self): 1212 | idx = 0 1213 | self.tipymax = self.rootymax = 0 1214 | self.tipymin = self.rootymin = 10000 1215 | self.idxl = len(self.rootprofile) / 4 # trailing edge limiting index limit 1216 | self.actualrootthickmax = 0 # find the actual root thickness max 1217 | self.actualrootthickmin = 10000 # find the actual root thickness min 1218 | 1219 | self.root = list() 1220 | div = 0.0 # if first value is not 1.0 then set this so that all values can be scaled to 1 1221 | 1222 | for line in self.rootprofile: 1223 | bits = line.split() 1224 | if len(bits): 1225 | 1226 | #scaling, some dat files are 0..1 and some are 0..100 1227 | if div == 0.0: 1228 | div = float(bits[0]) 1229 | 1230 | x = 1 - float(bits[0]) / div 1231 | y = float(bits[1]) / div 1232 | self.root.append( [0,0] ) 1233 | self.root[idx][0] = x * self.rootlength 1234 | self.root[idx][1] = y * self.rootlength 1235 | # want to know the actual root profile thickness to crosscheck against foamthick 1236 | art = y * self.rootchord 1237 | if self.actualrootthickmax < art: 1238 | self.actualrootthickmax = art 1239 | if self.actualrootthickmin > art: 1240 | self.actualrootthickmin = art 1241 | 1242 | if self.rootymax < self.root[idx][1]: 1243 | self.rootymax = self.root[idx][1] 1244 | if self.rootymin > self.root[idx][1]: 1245 | self.rootymin = self.root[idx][1] 1246 | 1247 | idx = idx + 1 1248 | 1249 | self.actualrootthick = self.actualrootthickmax - self.actualrootthickmin; 1250 | #end FindThicknessRoot 1251 | 1252 | # creates the self.tip array of coordinates 1253 | def CreateTip(self): 1254 | idx = 0 1255 | self.tgtravel = 0 # tip gantry travel, essentially tip length at gantry 1256 | self.tip = list() 1257 | div = -1 1258 | for line in self.tipprofile: 1259 | bits = line.split() 1260 | if len(bits): 1261 | if div == -1: 1262 | div = float(bits[0]) 1263 | x = 1 - float(bits[0]) / div #bits[0] is x 1264 | y = float(bits[1]) / div #bits[1] is y 1265 | self.tip.append( [0,0] ) 1266 | self.tip[idx][0] = x * self.tiplength 1267 | self.tip[idx][1] = y * self.tiplength 1268 | 1269 | idx = idx + 1 1270 | #end CreateTip 1271 | 1272 | #find the thicknesses for tip, do this after rotate!, coords are in mm 1273 | def FindThicknessesTip(self): 1274 | idx = 0 1275 | self.tgtravel = 0 # tip gantry travel, essentially tip length at gantry 1276 | self.tipymax = 0 1277 | self.tipymin = 10000 1278 | 1279 | for tip in self.tip: 1280 | x = tip[0] #is x 1281 | y = tip[1] #is y 1282 | # tip gantry travel - for drawing 1283 | if (x > self.tgtravel): 1284 | self.tgtravel = x 1285 | 1286 | if self.tipymax < y: 1287 | self.tipymax = y 1288 | if self.tipymin > y: 1289 | self.tipymin = y 1290 | 1291 | idx = idx + 1 1292 | #end FindThicknessTip 1293 | 1294 | def TrailingEdgeLimits1(self): 1295 | # do trailing edge limiting for both profiles 1296 | c = len(self.tip) -1 1297 | for idx in range(2, self.idxl): 1298 | other = c - idx; 1299 | dist = self.tip[idx][1] - self.tip[other][1] 1300 | if (dist < self.trail): 1301 | adjust = (self.trail - dist) / 2 1302 | self.tip[idx][1] += adjust 1303 | self.tip[other][1] -= adjust 1304 | 1305 | dist = self.root[idx][1] - self.root[other][1] 1306 | if (dist < self.trail): 1307 | adjust = (self.trail - dist) / 2; 1308 | self.root[idx][1] += adjust; 1309 | self.root[other][1] -= adjust; 1310 | #end TrailingEdgeLimits 1311 | 1312 | #new way, move the top to be trail above the bottom surface 1313 | def TrailingEdgeLimits2(self): 1314 | # do trailing edge limiting for both profiles 1315 | for this in range(0, self.idxl): 1316 | other = len(self.root) - 1 - this # index of the bottom surface point 1317 | dist = self.root[this][1] - self.root[other][1] 1318 | if dist < self.trail: 1319 | self.root[this][1] = self.root[other][1] + self.trail 1320 | dist = self.tip[this][1] - self.tip[other][1] 1321 | if dist < self.trail: 1322 | self.tip[this][1] = self.tip[other][1] + self.trail 1323 | 1324 | #end TrailingEdgeLimits 1325 | 1326 | def FindStartPoints(self): 1327 | # do foam stuff and calc start height 1328 | # might need to adjust this to account for rotation of the tip profile putting its max/min beyond the root profile 1329 | if self.unit: 1330 | off = 0.25 1331 | else: 1332 | off = 6 #min offet from outside of foam 1333 | if (self.foamthickness == 0): 1334 | print "foamthick = 0" 1335 | self.foamthickness = self.rootymax - self.rootymin + off 1336 | self.g_code.insert(END, " foamthickness calculated at " + self.Format(self.foamthickness,2) + self.units) 1337 | self.sp = ((self.foamthickness - self.skintop - self.skinbot) * -self.rootymin / (self.rootymax - self.rootymin)) + self.skinbot 1338 | else: 1339 | if (self.actualrootthick > (self.foamthickness - self.skintop - self.skinbot) ): 1340 | print "too thick" 1341 | self.g_code.insert(END," ERROR: actualroothickness %.2f EXCEEDS foamthickness %.2f" % (self.actualrootthick, self.foamthickness)) 1342 | self.g_code.insert(END," Recalculating foamthickness\n") 1343 | self.foamthickness = round(self.actualrootthick + off,0) 1344 | self.skintop = self.skinbot = 0 1345 | self.sp = ((self.foamthickness - self.skintop - self.skinbot) * -self.rootymin / (self.rootymax - self.rootymin)) + self.skinbot 1346 | else: 1347 | # startpoint, calculate it to put wing centered in foamthick less skins 1348 | self.rt = self.rootymax - self.rootymin 1349 | self.g_code.insert(END, "\n root thickness %f%s\n foamthickness %f%s\n" % (self.actualrootthick,self.units, self.foamthickness,self.units)) 1350 | if (self.skintop or self.skinbot): 1351 | t = self.foamthickness - self.skintop - self.skinbot 1352 | self.g_code.insert(END," (%f after skinning)",(t)) 1353 | 1354 | #max height of profiles 1355 | rat = max(self.rootymax,self.tipymax) - min(self.rootymin, self.tipymin) 1356 | #max off set from bottom 1357 | h = -min(self.rootymin , self.tipymin) 1358 | #print "rat %.3f h %.3f" % (rat, h) 1359 | leftover = (self.foamthickness - self.skintop - self.skinbot) - rat 1360 | self.sp = leftover/2 + h + self.skinbot 1361 | 1362 | #now check that it still fits the foam 1363 | if ((self.sp + self.rootymax) > self.foamthickness) or (self.sp + self.tipymax > self.foamthickness): 1364 | self.g_code.insert(END, "ERROR: top of profile will exit the foam, need thicker foam!\n") 1365 | 1366 | # do some calcs for the BOTH output 1367 | self.need = 0 1368 | if ( (rat*2+2*off) > (self.foamthickness - self.skintop - self.skinbot) ) : 1369 | self.g_code.insert(END," WARNING: FOAMTHICK is not enough to cut BOTH panels\n") 1370 | self.need = rat*2 + off*2 1371 | self.g_code.insert(END, " WARNING: need foam at least %0.2f%s thick\n" % (self.need, self.units) ) 1372 | self.fth = (self.foamthickness / 2) 1373 | 1374 | self.spl = self.fth + ( (self.fth-self.skintop) - rat)/2 + h # left start point in top half 1375 | self.spr = self.fth - ( (self.fth-self.skinbot) - rat)/2 - h # right start point in bottom half 1376 | 1377 | bottomoftop = self.spl + min(self.rootymin, self.tipymin) 1378 | topofbottom = self.spr - min(self.rootymin, self.tipymin) 1379 | #print "spl %.3f spr %.3f bottomoftop %.3f topofbottom %.3f" % (self.spl, self.spr, bottomoftop, topofbottom) 1380 | 1381 | if (bottomoftop < topofbottom): 1382 | self.g_code.insert(END, "ERROR: sections are colliding in BOTH, need thicker foam by %.3f \n" % (topofbottom - bottomoftop)) 1383 | 1384 | #end of FindStartPoints 1385 | 1386 | #RESAMPLE stuff 1387 | 1388 | #calculate new y for this $x, from the points on the line x1,y1 to x2,y2 1389 | def newy(self, x1,y1,x2,y2,x): 1390 | m = (y2-y1) / (x2-x1) 1391 | c = y1 - m * x1 1392 | y = m * x + c 1393 | return y 1394 | 1395 | #resample sla to have the same number of points as master 1396 | #master must have more points than slave 1397 | # the two lists are the raw lines from the dat file 1398 | def resample(self, mas,sla): 1399 | #mmin point in master 1400 | mmin = 1000 1401 | prec = 7 1402 | idx = 0 1403 | midx = -1 1404 | mdiv = -1 1405 | for m in mas: 1406 | bits = m.split() 1407 | if mdiv < 0: 1408 | mdiv = float(bits[0]) 1409 | x = float(bits[0]) / mdiv # must scale all values 1410 | y = float(bits[1]) / mdiv 1411 | 1412 | if x < mmin: 1413 | midx = idx 1414 | mmin = x 1415 | idx = idx + 1 1416 | #print("mmin %0.7s midx %d mdiv %0.7f" % (mmin,midx,mdiv)) 1417 | #find smin point in sla 1418 | idx = 0 1419 | smin = 1000 1420 | sdiv = -1 1421 | for s in sla: 1422 | bits = s.split() 1423 | if sdiv < 0 : 1424 | sdiv = float(bits[0]) 1425 | x = float(bits[0]) / sdiv 1426 | y = float(bits[1]) / sdiv 1427 | if x < smin: 1428 | smin = x 1429 | sidx = idx 1430 | idx = idx + 1 1431 | #print("smin %0.7s sidx %d sdiv %0.7f mdiv %0.7f" % (smin,sidx,sdiv,mdiv)) 1432 | 1433 | new = list() 1434 | top = 1 1435 | idx = 0 1436 | for m in mas: 1437 | #if top: print "%d %s %d" %(idx,m, top) 1438 | bits = m.split() 1439 | cx = float(bits[0]) / mdiv 1440 | cy = float(bits[1]) / mdiv 1441 | #print "cx %.7f top %d" % (cx,top) 1442 | if (cx < (mmin + 0.00000001)): 1443 | top = 0 1444 | #print str(cx) + " change to bottom" + str(top) + " idx = " + str(idx) + "\n" 1445 | if (top == 1): 1446 | for i in range(0,sidx+1): 1447 | bits = sla[i].split() 1448 | sx = float(bits[0]) / sdiv 1449 | sy = float(bits[1]) / sdiv 1450 | 1451 | if abs(cx - sx) < 0.000001: 1452 | #print "%d cx=sx appending sla %f,%f" % (idx,sx,sy) 1453 | new.append("%0.7f %0.7f" % (sx,sy)) # echo "top equal\n"; 1454 | break 1455 | 1456 | bits1 = sla[i+1].split() #split next line 1457 | sx1 = float(bits1[0]) / sdiv 1458 | sy1 = float(bits1[1]) / sdiv 1459 | 1460 | if mmin < smin: 1461 | if (abs(cx - mmin) < 0.001): 1462 | #print "sx %.7f smin %.7f" % (sx,smin) 1463 | if (abs(sx - smin) < 0.001): 1464 | #print " sx == smin" 1465 | new.append("%0.7f %0.7f" % (cx , sy)) 1466 | break 1467 | if (cx > sx1): 1468 | if abs(sx - sx1) < 0.000001: 1469 | #print "%d sx = sx1 appending sla %f,%f" % (idx,cx,sy) 1470 | new.append("%0.7f %0.7f" % (cx , sy)) 1471 | else: 1472 | # calculate a new Y at this X 1473 | y = self.newy(sx1,sy1, sx, sy, cx ) 1474 | #print "%d calc new Y at %f = %f,%f " %(idx, cx,cx,y) 1475 | new.append("%0.7f %0.7f" % (cx, y)) # echo "top new $cx $y\n"; 1476 | break 1477 | if top == 0: # echo "bottom "; 1478 | for i in range(sidx, len(sla)-1): 1479 | #print "i %d" % i 1480 | bits = sla[i].split() 1481 | sx = float(bits[0]) / sdiv 1482 | sy = float(bits[1]) / sdiv 1483 | if abs(cx - sx) < 0.000001: 1484 | #print "%d CX=SX appending sla %f,%f" % (idx,sx,sy) 1485 | new.append("%0.7f %0.7f" % (sx,sy)) # echo "top equal\n"; 1486 | break 1487 | 1488 | bits1 = sla[i+1].split() #split next line 1489 | sx1 = float(bits1[0]) / sdiv 1490 | sy1 = float(bits1[1]) / sdiv 1491 | 1492 | if (cx < sx1): 1493 | if abs(sx - sx1) < 0.000001: 1494 | #print "%d SX=SX1 %.7f,%.7f appending sla %f,%f" % (idx,sx,sx1, sx,sy) 1495 | new.append("%0.7f %0.7f" % (cx , sy)) 1496 | else: 1497 | # calculate a new Y at this X 1498 | #print "%d sx %.6f sx1 %.6f" % (idx,sx,sx1) 1499 | y = self.newy(sx,sy, sx1, sy1, cx ) 1500 | #print "%d CALC new Y at %f = %f,%f " %(idx, cx,cx,y) 1501 | new.append("%0.7f %0.7f" % (cx , y)) 1502 | break 1503 | idx = idx + 1 1504 | # now add last one 1505 | #print new 1506 | bits = sla[ len(sla)-1].split() 1507 | sx = float(bits[0]) / sdiv 1508 | sy = float(bits[1]) / sdiv 1509 | #print "%d CX=sx appending sla %f,%f" % (idx,sx,sy) 1510 | new.append("%0.7f %0.7f" % (sx,sy)) 1511 | #print len(mas), len(sla), len(new) 1512 | return new 1513 | #end resample 1514 | 1515 | #ROTATE 1516 | #from the web https://stackoverflow.com/questions/20023209/function-for-rotating-2d-objects 1517 | def rotatePolygon(self,polygon,theta): 1518 | """Rotates the given polygon which consists of corners represented as (x,y), 1519 | around the ORIGIN, clock-wise, theta degrees""" 1520 | theta = radians(theta) 1521 | rotatedPolygon = list() 1522 | for corner in polygon : 1523 | rotatedPolygon.append([ corner[0]*cos(theta)-corner[1]*sin(theta) , corner[0]*sin(theta)+corner[1]*cos(theta)] ) 1524 | return rotatedPolygon 1525 | 1526 | 1527 | app = Application() 1528 | app.master.title('Wing hotwire G-Code Generator') 1529 | app.mainloop() 1530 | -------------------------------------------------------------------------------- /wing.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # python wing.pyw 4 | # Version $Id: wing.pyw 1.1 2021/01/19 07:02:33 swarf Exp $ 5 | # Dec 4 2007 6 | # XYUV wing G-Code Generator for EMC2 7 | # also YZ/XZ for GRBL for straight wings only 8 | # also XYUZ for GRBL 4axis 9 | """ 10 | Copyright (C) <2008> 11 | 12 | This program is free software: you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation, either version 3 of the License, or 15 | (at your option) any later version. 16 | 17 | This program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program. If not, see . 24 | 25 | e-mail me any suggestions to "jet1024 at semo dot net" 26 | If you make money using this software 27 | you must donate self.20 USD to a local food bank 28 | or the food police will get you! Think of others from time to time... 29 | To make it a menu item in Ubuntu use the Alacarte Menu Editor and add 30 | the command python YourPathToThisFile/face.py 31 | make sure you have made the file execuatble by right 32 | clicking and selecting properties then Permissions and Execute 33 | To use with EMC2 see the instructions at: 34 | http:#wiki.linuxcnc.org/cgi-bin/emcinfo.pl?Simple_EMC_G-Code_Generators 35 | 36 | 2008-02-24 Rick Calder "rick at llamatrails dot com" 37 | Added option/code to select X0-Y0 position: Left-Rear or Left-Front 38 | To change the default, change line 171: 4=Left-Rear, 5=Left-Front 39 | 40 | 2010-01-06 Brad Hanken "chembal at gmail dot com" 41 | Added option and code to change the lead in and lead out amount 42 | If nothing is entered, the old calculated value of tool radius + .1 is still used 43 | 44 | 2014-11-00 swarfer: made metric the default 45 | add option to cut unidirectional 46 | 47 | 2017-11-16 swarfer: modified into a wing cutter, based on gwing.php by the swarfer 48 | python 2.x only... 49 | 50 | 2018-02-06 swarfer: cut wings in YZ or XZ only, straight wings on a gantry XYZ machine 51 | 2020-05-10 swarfer: add XYUZ output for 4axis GRBL from rckeith 52 | 2021-10-14 rcKeith: Python3 compatibility 53 | 2021-01-19 swarfer: fine tune for release for Python3 54 | """ 55 | 56 | from tkinter import * 57 | from math import * 58 | from tkinter.simpledialog import * 59 | from tkinter.filedialog import * 60 | import configparser 61 | from decimal import * 62 | import tkinter.messagebox 63 | import os 64 | import glob 65 | import string 66 | import re 67 | 68 | # LinuxCNC variable 69 | IN_AXIS = "AXIS_PROGRESS_BAR" in os.environ 70 | 71 | class Application(Frame): 72 | def __init__(self, master=None): 73 | Frame.__init__(self, master, bd=1, padx=10) 74 | self.grid() 75 | self.pack() 76 | self.createMenu() 77 | 78 | 79 | if IN_AXIS: 80 | self.ext = '.ngc' 81 | else: 82 | self.ext = '.nc' 83 | 84 | self.inifile = os.path.join('.','wing.ini') 85 | try: 86 | self.DatDir = self.GetIniData(self.inifile,'Directories','DatFiles') 87 | except: 88 | # does not exist so write a default value 89 | self.DatDir = os.path.join('.','coord') 90 | self.WriteIniData(self.inifile,'Directories','DatFiles',self.DatDir) 91 | 92 | try: 93 | """ save in ini in the NC folder """ 94 | self.NcFileDirectory = self.GetIniData(self.inifile,'Directories','NcFiles') 95 | except: 96 | tkinter.messagebox.showinfo('Missing INI Data', 'You must set the\n' \ 97 | 'G-code File Directory\n' \ 98 | 'before saving a file.\n' \ 99 | 'Go to Edit/G-code Directory\n' \ 100 | 'in the menu to set this option') 101 | return 102 | #Pad Rows 103 | for row in range(1,9): 104 | self.rowconfigure(row,pad=5) 105 | 106 | self.createWidgets() 107 | 108 | #center the window 109 | self.update_idletasks() 110 | width = self.winfo_width() 111 | frm_width = self.winfo_rootx() - self.winfo_x() 112 | win_width = width + 2 * frm_width 113 | height = self.winfo_height() 114 | titlebar_height = self.winfo_rooty() - self.winfo_y() 115 | win_height = height + titlebar_height + frm_width 116 | x = self.winfo_screenwidth() # 2 - win_width # 2 117 | y = self.winfo_screenheight() # 2 - win_height # 2 118 | #self.At(1060,200) 119 | #self.deiconify() 120 | try: #try to read the last model saved 121 | modelname = self.GetIniData(self.inifile,'autoload','model') 122 | filename = os.path.join(self.NcFileDirectory, modelname + '.ini') 123 | if os.path.exists(filename): 124 | self.ReadModel(filename) 125 | except: 126 | pass 127 | 128 | 129 | def createMenu(self): 130 | #Create the Menu base 131 | self.menu = Menu(self) 132 | #Add the Menu 133 | self.master.config(menu=self.menu) 134 | #Create our File menu 135 | self.FileMenu = Menu(self.menu,tearoff =FALSE) 136 | #Add our Menu to the Base Menu 137 | self.menu.add_cascade(label='File', menu=self.FileMenu) 138 | #self.menu.option_add() 139 | #Add items to the menu 140 | #self.FileMenu.add_command(label='New', command=self.Simple) 141 | #self.FileMenu.add_command(label='Open', command=self.Simple) 142 | #self.FileMenu.add_separator() 143 | self.FileMenu.add_command(label='Quit', command=self.quit) 144 | 145 | self.EditMenu = Menu(self.menu, tearoff =FALSE) 146 | self.menu.add_cascade(label='Edit', menu=self.EditMenu) 147 | self.EditMenu.add_command(label='Copy', command=self.CopyClpBd) 148 | self.EditMenu.add_command(label='Select All', command=self.SelectAllText) 149 | self.EditMenu.add_command(label='Delete All', command=self.ClearTextBox) 150 | self.EditMenu.add_separator() 151 | #self.EditMenu.add_command(label='Preferences', command=self.Simple) 152 | self.EditMenu.add_command(label='G-Code Directory', command=self.NcFileDirectory) 153 | self.EditMenu.add_command(label='DAT (Airfoils) Directory', command=self.DatFileDirectory) 154 | 155 | self.ModelMenu = Menu(self.menu,tearoff=FALSE) 156 | self.menu.add_cascade(label='Model', menu=self.ModelMenu) 157 | self.ModelMenu.add_command(label='Load Model', command=self.LoadModel) 158 | self.ModelMenu.add_command(label='Save Model', command=self.SaveModel) 159 | 160 | 161 | self.HelpMenu = Menu(self.menu,tearoff =FALSE ) 162 | self.menu.add_cascade(label='Help', menu=self.HelpMenu) 163 | #self.HelpMenu.add_command(label='Help Info', command=self.HelpInfo) 164 | self.HelpMenu.add_command(label='About', command=self.HelpAbout) 165 | 166 | 167 | 168 | def createWidgets(self): 169 | self.sp1 = Label(self) 170 | self.sp1.grid(row=0) 171 | 172 | self.st1 = Label(self, text='Model Name ') 173 | self.st1.grid(row=1, column=0, sticky=E) 174 | self.ModelNameVar = StringVar() 175 | self.ModelNameVar.set('default') 176 | self.ModelName = Entry(self, width=20, textvariable=self.ModelNameVar) 177 | self.ModelName.grid(row=1, column=1, sticky=W) 178 | self.ModelName.focus_set() 179 | 180 | self.SaveModelButton = Button(self, text='Save Model',command=self.SaveModel) 181 | self.SaveModelButton.grid(row=1, column=2) 182 | 183 | self.LoadModelButton = Button(self, text='Load Model',command=self.LoadModel) 184 | self.LoadModelButton.grid(row=1, column=3,pady=3) 185 | 186 | 187 | self.st2 = Label(self, text='WingSpan ') 188 | self.st2.grid(row=2, column=0, sticky=E) 189 | self.WingSpanVar = StringVar() 190 | self.WingSpanVar.set('500') 191 | self.WingSpan = Entry(self, width=10, textvariable=self.WingSpanVar) 192 | self.WingSpan.grid(row=2, column=1, sticky=W) 193 | 194 | self.st22 = Label(self, text='Washout ') 195 | self.st22.grid(row=2, column=2, sticky=E) 196 | self.WashoutVar = StringVar() 197 | self.WashoutVar.set('0') 198 | self.Washout = Entry(self, width=10, textvariable=self.WashoutVar) 199 | self.Washout.grid(row=2, column=3, sticky=W) 200 | 201 | 202 | self.st3 = Label(self, text='Root Chord ') 203 | self.st3.grid(row=3, column=0, sticky=E) 204 | self.RootChordVar = StringVar() 205 | self.RootChordVar.set('200') 206 | self.RootChord = Entry(self, width=10, textvariable=self.RootChordVar) 207 | self.RootChord.grid(row=3, column=1, sticky=W) 208 | 209 | self.st4 = Label(self, text='Tip Chord ') 210 | self.st4.grid(row=3, column=2, sticky=E) 211 | self.TipChordVar = StringVar() 212 | self.TipChordVar.set('190') 213 | self.TipChord = Entry(self, width=10, textvariable=self.TipChordVar) 214 | self.TipChord.grid(row=3, column=3, sticky=W) 215 | 216 | 217 | self.profiles = glob.glob(os.path.join(self.DatDir, '*.dat')) 218 | self.profiles.sort() 219 | 220 | self.st5 = Label(self, text='Root Profile ') 221 | self.st5.grid(row=4, column=0, sticky=E) 222 | self.rootScrollbar = Scrollbar(self, orient=VERTICAL) 223 | self.RootProfilelistbox = Listbox(self, exportselection=0, height=5, width=18, yscrollcommand=self.rootScrollbar.set) 224 | self.rootScrollbar.config(command=self.RootProfilelistbox.yview) 225 | self.rootScrollbar.grid(row=4,column=1, sticky=N+S+E) 226 | self.RootProfilelistbox.grid(row=4, column=1, sticky=W) 227 | 228 | for item in self.profiles: 229 | nitem = str.replace(item,self.DatDir + os.sep,'') 230 | self.RootProfilelistbox.insert(END, nitem) 231 | if (len(self.profiles) > 0): 232 | self.RootProfilelistbox.selection_set(0) 233 | 234 | # self.st6 = Label(self, text='Tip Profile ') 235 | # self.st6.grid(row=4, column=2, sticky=E) 236 | # self.TipProfileVar = StringVar() 237 | # self.TipProfile = Entry(self, width=10, textvariable=self.TipProfileVar) 238 | # self.TipProfile.grid(row=4, column=3, sticky=W) 239 | 240 | self.st6 = Label(self, text='Tip Profile ') 241 | self.st6.grid(row=4, column=2, sticky=E) 242 | self.tipScrollbar = Scrollbar(self, orient=VERTICAL) 243 | self.TipProfilelistbox = Listbox(self, exportselection=0, height=5, yscrollcommand=self.tipScrollbar.set) 244 | self.tipScrollbar.config(command=self.TipProfilelistbox.yview) 245 | self.tipScrollbar.grid(row=4,column=4, sticky=N+S+W) 246 | self.TipProfilelistbox.grid(row=4, column=3, sticky=W) 247 | 248 | for item in self.profiles: 249 | nitem = str.replace(item,self.DatDir + os.sep,'') 250 | self.TipProfilelistbox.insert(END, nitem) 251 | if (len(self.profiles) > 0): 252 | self.TipProfilelistbox.selection_set(0) 253 | 254 | self.st7 = Label(self, text='Foam Chord ') 255 | self.st7.grid(row=5, column=0, sticky=E) 256 | self.FoamChordVar = StringVar() 257 | self.FoamChordVar.set(220) 258 | self.FoamChord = Entry(self, width=10, textvariable=self.FoamChordVar) 259 | self.FoamChord.grid(row=5, column=1, sticky=W) 260 | 261 | self.st8 = Label(self, text='Foam Thickness ') 262 | self.st8.grid(row=5, column=2, sticky=E) 263 | self.FoamThicknessVar = StringVar() 264 | self.FoamThicknessVar.set(50) 265 | self.FoamThickness = Entry(self, width=10, textvariable=self.FoamThicknessVar) 266 | self.FoamThickness.grid(row=5, column=3, sticky=W) 267 | 268 | 269 | self.st9 = Label(self, text='Trailing Edge Limit ') 270 | self.st9.grid(row=6, column=0, sticky=E) 271 | self.TrailingEdgeLimitVar = StringVar() 272 | self.TrailingEdgeLimitVar.set(3) 273 | self.TrailingEdgeLimit = Entry(self, width=10, textvariable=self.TrailingEdgeLimitVar) 274 | self.TrailingEdgeLimit.grid(row=6, column=1, sticky=W) 275 | 276 | self.st10 = Label(self, text='Leading Edge Sweep ') 277 | self.st10.grid(row=6, column=2, sticky=E) 278 | self.LeadingEdgeSweepVar = StringVar() 279 | self.LeadingEdgeSweepVar.set(5) 280 | self.LeadingEdgeSweep = Entry(self, width=10, textvariable=self.LeadingEdgeSweepVar) 281 | self.LeadingEdgeSweep.grid(row=6, column=3, sticky=W) 282 | 283 | 284 | #self.st11 = Label(self, text='Gantry Length ') KH 285 | self.st11 = Label(self, text='Carriage Length ') 286 | self.st11.grid(row=7, column=0, sticky=E) 287 | self.GantryLengthVar = StringVar() 288 | self.GantryLengthVar.set(600) 289 | self.GantryLength = Entry(self, width=10, textvariable=self.GantryLengthVar) 290 | self.GantryLength.grid(row=7, column=1, sticky=W) 291 | 292 | self.st12 = Label(self, text='Feedrate ') 293 | self.st12.grid(row=7, column=2, sticky=E) 294 | self.FeedrateVar = StringVar() 295 | self.FeedrateVar.set(50) 296 | self.Feedrate = Entry(self, width=10, textvariable=self.FeedrateVar) 297 | self.Feedrate.grid(row=7, column=3, sticky=W) 298 | 299 | #these two need to be radio boxes 300 | 301 | # Units Radiobutton Callback # 5 302 | # def radCallUnit(): 303 | # radSel=radVar.get() 304 | # if radSel == 1: win.configure(background=COLOR1) 305 | # elif radSel == 2: win.configure(background=COLOR2) 306 | # elif radSel == 3: win.configure(background=COLOR3) 307 | 308 | # create two Radiobuttons 309 | self.XYsideVar = IntVar() # 0 for XY right, 1 for XY left 310 | self.st13 = Label(self, text='XY side ') 311 | self.st13.grid(row=8, column=0, sticky=E) 312 | urad1 = Radiobutton(self, text='Right', variable=self.XYsideVar, value=0) 313 | urad1.grid(row=8, column=1, sticky=E) 314 | urad2 = Radiobutton(self, text='Left', variable=self.XYsideVar, value=1) 315 | urad2.grid(row=8, column=1, sticky=W) 316 | self.XYsideVar.set(0) 317 | 318 | self.st14=Label(self,text='Units : ') 319 | self.st14.grid(row=8,column=2) 320 | UnitOptions=[('Inch',1,'E'),('MM',0,'W')] 321 | self.UnitVar = IntVar() 322 | for text, value, side in UnitOptions: 323 | Radiobutton(self, text=text,value=value, variable=self.UnitVar,indicatoron=0,width=6,).grid(row=8, column=3,sticky=side) 324 | self.UnitVar.set(0) 325 | # XYUV/YZ/XZ 326 | self.XYUVVar = IntVar() # 0 for XYUV , 1 for YZ, 2 for XZ, 3 for XYUZ (GRBL mode) 327 | #self.st13 = Label(self, text='XYUV/YZ/XZ/XYUZ ') 328 | #self.st13.grid(row=9, column=0, sticky=E) 329 | urad1 = Radiobutton(self, text='XYUV', variable=self.XYUVVar, value=0) 330 | urad1.grid(row=9, column=0, sticky=W) 331 | urad2 = Radiobutton(self, text='YZ only', variable=self.XYUVVar, value=1) 332 | urad2.grid(row=9, column=1, sticky=W) 333 | urad1 = Radiobutton(self, text='XZ only', variable=self.XYUVVar, value=2) 334 | urad1.grid(row=9, column=2, sticky=W) 335 | urad3 = Radiobutton(self, text='XYUZ(GRBL)', variable=self.XYUVVar, value=3) 336 | urad3.grid(row=9, column=3, sticky=W) 337 | self.XYUVVar.set(3) 338 | 339 | self.spacer3 = Label(self, text='') 340 | self.spacer3.grid(row=10, column=0, columnspan=5) 341 | # gcode block 342 | self.g_code = Text(self,width=40,height=14,bd=3) 343 | self.g_code.grid(row=11, column=0, columnspan=4, sticky=E+W+N+S,pady=5) 344 | self.tbscroll = Scrollbar(self,command = self.g_code.yview) 345 | self.tbscroll.grid(row=11, column=4, sticky=N+S+W) 346 | self.g_code.configure(yscrollcommand = self.tbscroll.set) 347 | 348 | self.sp4 = Label(self) 349 | self.sp4.grid(row=12) 350 | 351 | #make sure these exist so pressing save button before generate does not crash 352 | self.g_code_left = list() 353 | self.g_code_right = list() 354 | self.g_code_both = list() 355 | 356 | self.GenButton = Button(self, text='Generate G-Code',justify=CENTER,command=self.GenCode) 357 | self.GenButton.grid(row=12, column=0) 358 | 359 | # self.GenButton = Button(self, text='Generate G-Code bi',command=self.GenCode2) 360 | # self.GenButton.grid(row=12, column=1) 361 | 362 | # self.CopyButton = Button(self, text='Select All & Copy',command=self.SelectCopy) 363 | # self.CopyButton.grid(row=12, column=2) 364 | 365 | self.WriteButton = Button(self, text='Write to Files', justify=CENTER,command=self.WriteToFile) 366 | self.WriteButton.grid(row=12, column=1) 367 | 368 | 369 | 370 | 371 | 372 | if IN_AXIS: 373 | self.quitButton1 = Button(self, text='Write LEFT to AXIS and Quit', command=self.WriteLeftToAxis) 374 | self.quitButton1.grid(row=12, column=2, sticky=E) 375 | self.quitButton2 = Button(self, text='Write RIGHT to AXIS and Quit', command=self.WriteRightToAxis) 376 | self.quitButton2.grid(row=12, column=3, sticky=E) 377 | self.quitButton3 = Button(self, text='Write BOTH to AXIS and Quit', command=self.WriteBothToAxis) 378 | self.quitButton3.grid(row=12, column=4, sticky=E) 379 | else: 380 | self.quitButton = Button(self, text=' Quit Wing G-code', justify=CENTER,command=self.MyQuit) 381 | self.quitButton.grid(row=12, column=3 ,pady=8) 382 | 383 | 384 | 385 | def MyQuit(self): 386 | if IN_AXIS: 387 | sys.stdout.write('%') 388 | self.quit() 389 | else: 390 | self.quit() 391 | #python makes number formatting so hard.... 392 | def Format(self,val,dec): 393 | s = '%0.' + str(int(dec)) + 'f' 394 | return s % val 395 | 396 | #get all the values from the widgets 397 | def GetWValues(self): 398 | self.modelname = self.ModelNameVar.get() 399 | self.wingspan = self.FToD(self.WingSpanVar.get()) 400 | self.washout = self.FToD(self.WashoutVar.get()) 401 | self.rootchord = self.FToD(self.RootChordVar.get()) 402 | self.tipchord = self.FToD(self.TipChordVar.get()) 403 | 404 | items = self.RootProfilelistbox.curselection() 405 | self.rootfile = self.RootProfilelistbox.get(items[0]) 406 | #do not use ACTIVE for this 407 | items = self.TipProfilelistbox.curselection() 408 | self.tipfile = self.TipProfilelistbox.get(items[0]) 409 | 410 | self.foamchord = self.FToD(self.FoamChordVar.get()) 411 | self.foamthickness = self.FToD(self.FoamThicknessVar.get()) 412 | self.trail = self.FToD(self.TrailingEdgeLimitVar.get()) 413 | self.sweep = self.FToD(self.LeadingEdgeSweepVar.get()) 414 | self.gantry = self.FToD(self.GantryLengthVar.get()) 415 | self.feedrate = self.FToD(self.FeedrateVar.get()) 416 | if self.feedrate == 0: 417 | self.feedrate = 1 418 | self.g_code.insert(END,'WARNING: feedrate cannot be ZERO\n') 419 | self.xy = self.XYsideVar.get() 420 | self.xyuv = self.XYUVVar.get() 421 | if self.xyuv == 3: 422 | self.xyuv = 0 423 | self.grblmode = True 424 | else: 425 | self.grblmode = False 426 | self.unit = self.UnitVar.get() 427 | if self.unit: 428 | self.units = '"' 429 | else: 430 | self.units = 'mm' 431 | 432 | 433 | def Header(self,alist): 434 | alist.append('%\n') 435 | line = 'G17\nG90\n' 436 | if self.UnitVar.get()==1: 437 | line = line + 'G20\n' 438 | dec = 3 439 | self.Safe = 0.1 # 0.1 inch 440 | else: 441 | line = line + 'G21\n' 442 | self.Safe = 1 #1 mm 443 | dec = 1 444 | if self.grblmode == FALSE: 445 | line = line + 'M3 S100 ' 446 | if len(self.FeedrateVar.get())>0: 447 | line = line + 'F%s\n' % self.FeedrateVar.get() 448 | else: 449 | line = line + '\n' 450 | alist.append(line) 451 | 452 | #these tell LinuxCNC where to put the XY and UV on the screen 453 | line = "(AXIS,XY_Z_POS,0)\n" 454 | alist.append(line) 455 | line = "(AXIS,UV_Z_POS,%.3f)\n" % (self.gantry) 456 | alist.append(line) 457 | 458 | alist.append('(modelname ' + self.modelname + ')\n') 459 | 460 | alist.append('(wingspan ' + self.Format(self.wingspan,dec) + ')\n') 461 | alist.append('(rootchord ' + self.Format(self.rootchord,dec) + ')\n') 462 | alist.append('(tipchord ' + self.Format(self.tipchord,dec) + ')\n') 463 | alist.append('(rootfile ' + self.rootfile + ')\n') 464 | alist.append('(tipfile ' + self.tipfile + ')\n') 465 | alist.append('(foamchord ' + self.Format(self.foamchord,dec) + ')\n') 466 | alist.append('(foamthickness ' + self.Format(self.foamthickness,dec) + ')\n') 467 | alist.append('(trail ' + self.Format(self.trail,dec) + ')\n') 468 | alist.append('(sweep ' + self.Format(self.sweep,dec) + ')\n') 469 | alist.append('(washout ' + self.Format(self.washout,dec) + ')\n') 470 | alist.append('(carriage ' + self.Format(self.gantry,dec) + ')\n') 471 | alist.append('(feedrate ' + self.Format(self.feedrate,dec) + ')\n') 472 | if (self.xy == 0): 473 | alist.append('(xy right)\n') 474 | else: 475 | alist.append('(xy left)\n') 476 | if (self.unit == 0): 477 | alist.append('(unit MM)\n') 478 | else: 479 | alist.append('(unit inch)\n') 480 | alist.append('(NOTE 0,0 is wire on table at front foam corner)\n') 481 | 482 | 483 | def GenCode(self): 484 | """ will generate all three gcode files as string lists """ 485 | self.g_code_left = [] 486 | self.g_code_right = [] 487 | self.g_code_both = [] 488 | self.g_code.delete("1.0",END) 489 | self.GetWValues() 490 | if (self.wingspan >= self.gantry): 491 | self.g_code.insert(END,"PANIC: wingspan greater than carriage separation\n") 492 | return None 493 | #print "sweep %f" % self.sweep 494 | self.gap = (self.gantry - self.wingspan) / 2 # gap between end of wing and gantry on each side 495 | self.teoff = self.rootchord - self.sweep - self.tipchord # opposite of sweep 496 | if (self.teoff < 0): 497 | self.g_code.insert(END, " Swept Wing\n") 498 | #print "teoff %f" %self.teoff 499 | 500 | self.t1 = atan(self.sweep / self.wingspan) 501 | self.t2 = atan(self.teoff / self.wingspan) 502 | self.E1 = tan(self.t1) * self.gap 503 | self.E2 = tan(self.t2) * self.gap 504 | self.E3 = tan(self.t1) * (self.wingspan + self.gap) 505 | self.E4 = tan(self.t2) * (self.wingspan + self.gap) 506 | self.debug = 0 507 | if self.debug: 508 | print("E1 %0.4f" % self.E1) 509 | print("E2 %0.4f" % self.E2) 510 | print("E3 %0.4f" % self.E3) 511 | print("E4 %0.4f" % self.E4) 512 | 513 | self.rootlength = self.rootchord + self.E1 + self.E2 # gantry movement for root 514 | if (self.teoff < 0): 515 | self.waste = self.E1 # # wastage at trailing edge of tip profile 516 | else: 517 | self.waste = self.E2 # # wastage at trailing edge of root profile 518 | self.tiplength = self.rootchord - self.E3 - self.E4 # gantry movement for tip 519 | 520 | if (self.debug): 521 | print("rootlength %0.4f" % self.rootlength) 522 | print("tiplength %0.4f" % self.tiplength) 523 | 524 | if (self.tiplength < 0): 525 | self.g_code.insert(END, "\n PANIC: tip length is negative, I cannot plot this,\n you need to put the gantry closer together\n") 526 | return None 527 | 528 | self.toffset = self.E2 + self.E4 # tip offset for tip gantry 529 | if (self.debug): 530 | print("toffset %0.4f" % self.toffset) 531 | 532 | self.g_code.insert(END, "rootfile " + self.rootfile + '\n') 533 | self.g_code.insert(END, "tipfile " + self.tipfile + '\n') 534 | 535 | #root file 536 | if not(os.path.exists(self.rootfile)): 537 | self.rootfile = os.path.join(self.DatDir, self.rootfile) 538 | #tip 539 | if not(os.path.exists(self.tipfile)): 540 | self.tipfile = os.path.join(self.DatDir,self.tipfile) 541 | if (not(os.path.exists(self.rootfile)) or not(os.path.exists(self.tipfile))): 542 | self.g_code.insert(END, " PANIC: either root or tip file not found\n") 543 | return None 544 | 545 | 546 | # trailing edge thickness limit, does not work if number of points is low 547 | if (self.trail > 0): 548 | self.g_code.insert(END," Trailing edge " + str(self.trail) + '\n' ) 549 | 550 | # foam management 551 | self.skintop = 0 552 | self.skinbot = 0 553 | 554 | # assume start at trailing edge on root and tip+self.toffset profile, wire on platten at 0,0 555 | # then feed top surface 556 | # then bottom 557 | # then feed out 558 | 559 | with open(self.rootfile) as f: 560 | self.rootprofile = f.read().splitlines() 561 | with open(self.tipfile) as f: 562 | self.tipprofile = f.read().splitlines() 563 | 564 | self.rootprofile = self.stripfile(self.rootprofile) 565 | self.tipprofile = self.stripfile(self.tipprofile) # get rid of comment lines 566 | 567 | if (len(self.rootprofile) != len(self.tipprofile)): 568 | #self.g_code.insert(END," ERROR profile point counts not equal (%d,%d)\n" % (len(self.rootprofile) , len(self.tipprofile))) 569 | #return None 570 | if (len(self.rootprofile) > len(self.tipprofile)): 571 | self.g_code.insert(END, "root bigger, resampling tip") 572 | self.tipprofile = self.resample(self.rootprofile, self.tipprofile) 573 | else: 574 | self.g_code.insert(END,"tip bigger, resmapling root") 575 | self.rootprofile = self.resample(self.tipprofile, self.rootprofile) 576 | 577 | self.FindThicknessesRoot() 578 | if self.debug: print("Actualrootthick " + str(self.actualrootthick)) 579 | self.CreateTip() 580 | if self.washout != 0.0: 581 | self.tip = self.rotatePolygon(self.tip, -self.washout) # rotate nose down, keep trailing edge straight 582 | self.FindThicknessesTip() 583 | 584 | self.TrailingEdgeLimits2() 585 | self.FindStartPoints() 586 | 587 | # info 588 | self.g_code.insert(END, " startpoint sp= %0.3f%s above platten\n" % (self.sp, self.units) ) 589 | if self.need == 0: 590 | self.g_code.insert(END, " startpoint spL= %0.3f%s above platten\n" % (self.spl, self.units)) 591 | self.g_code.insert(END, " startpoint spR= %0.3f%s above platten\n" % (self.spr, self.units)) 592 | self.l = self.rootchord + self.waste 593 | if (self.debug): 594 | print("L %.2f waste %.2f" % (self.l,self.waste)) 595 | if (self.l > self.foamchord): 596 | self.g_code.insert(END, "WARNING: root length + waste(%.2f) is greater than the foam chord(%.2f) you specified\n" % (self.l, self.foamchord)) 597 | 598 | # Generate the G-Codes 599 | self.Header(self.g_code_left) 600 | self.Header(self.g_code_right) 601 | self.Header(self.g_code_both) 602 | 603 | if self.xy: 604 | self.g_code.insert(END, 'XY carriage left\n') 605 | else: 606 | self.g_code.insert(END, 'XY carriage right\n') 607 | # os.path.join(self.NcFileDirectory, self.modelname, '-right' + self.ext) 608 | # os.path.join(self.NcFileDirectory, self.modelname, '-left' + self.nc) 609 | # os.path.join(self.NcFileDirectory, self.modelname,'-both' + self.nc) 610 | 611 | if (self.xy): #XY on left 612 | self.plot(self.root, self.tip, 'right', self.g_code_right, self.sp, 1, 0) 613 | self.plot(self.tip, self.root,'left' , self.g_code_left, self.sp, 1, 0) 614 | if (self.need == 0): 615 | self.g_code.insert(END, "Doing BOTH file\n") 616 | self.plot(self.root, self.tip, 'right', self.g_code_both, self.spl, 1, 1) 617 | self.plot(self.root, self.tip, 'right' , self.g_code_both, self.spr, -1, 1) 618 | else: 619 | self.g_code.insert(END, "Did not output BOTH file since foam is not thick enough\n") 620 | else: #XY on right 621 | self.plot(self.root, self.tip, 'left', self.g_code_left, self.sp, 1, 0) 622 | self.plot(self.tip, self.root,'right' , self.g_code_right, self.sp, 1, 0) 623 | 624 | # output combined foils for GRBL XYUV 625 | if (self.need == 0): 626 | self.g_code.insert(END, "Doing BOTH file\n") 627 | self.plot(self.root, self.tip, 'left', self.g_code_both, self.spl, 1, 1) 628 | self.plot(self.root, self.tip, 'left' , self.g_code_both, self.spr, -1, 1) 629 | else: 630 | self.g_code.insert(END, "Did not output BOTH file since foam is not thick enough\n") 631 | 632 | 633 | #for line in self.g_code_left: 634 | # self.g_code.insert(END, line) 635 | """ 636 | p1 first airfoil data 637 | p2 seconds airfoil data 638 | direc 'left' or 'right' for direction 639 | fname the filename 640 | sp start point , distance above X Zero 641 | invert 1 for normal, -1 for invert 642 | flag 0 for normal, 1 for BOTH mode 643 | """ 644 | def plot(self, p1, p2, direc, flist, sp, invert, flag): 645 | #global $inch, $units, $trail, $E2, $wingspan, $params, $rootymax, $rootymin, $tipymax, $tipymin, self.toffset, $feedspeed, $rootlength, $tiplength, $waste, $foamthick, $foamchord, $skintop, $skinbot, $xy; 646 | # if (flag): 647 | # if (invert == 1): 648 | # flist.append("%%\n") 649 | # else: 650 | # flist.append( "%%\n"); 651 | flist.append( "(Generated by wing.py. David the Swarfer, 2017, rcKeith 2021 for GRBL )\n") 652 | flist.append( "(from ini file root " + os.path.basename(self.rootfile) + " tip " + os.path.basename(self.tipfile) + " offset %.2f )\n" % (self.toffset)) 653 | if (self.toffset > 0): 654 | flist.append( "(minimum material chord is %0.1f with wastage of %.3f at trailing edge)\n" % (self.rootlength, self.waste)) 655 | else: 656 | mmc = self.rootchord + self.waste 657 | w = -self.E2 658 | flist.append( "(Swept Wing)\n") 659 | flist.append( "(minimum material chord is %0.3f%s with wastage of %0.3f%s at trailing edge)\n" % (mmc, self.units,w,self.units)) 660 | if (self.xyuv != 0): 661 | flist.append("(generating cartesian gantry, taper ignored)\n") 662 | 663 | flist.append( "(root carriage thickness = %0.1f%s)\n" % (self.rootymax - self.rootymin, self.units)) 664 | flist.append( "(tip carriage thickness = %0.1f%s)\n" % (self.tipymax - self.tipymin, self.units)) 665 | if (self.rootlength != self.tiplength): 666 | flist.append( "(above sizes are for carriage travel, actual wing will be thinner)\n") 667 | if (self.foamchord and self.foamthickness): 668 | ws = self.wingspan 669 | flist.append( "(foam block %.3f'wingspan' x %.3f x %.3f%s)\n" % (ws,self.foamchord,self.foamthickness,self.units)); 670 | if (self.xyuv == 0): 671 | if (self.xy): 672 | flist.append( "(XY carriage left)\n") 673 | else: 674 | flist.append( "(XY carriage right)\n") 675 | else: 676 | if (self.xyuv == 1): 677 | self.g_code.insert(END, "YZ") 678 | flist.append( "(YZ carriage)\n") 679 | else: 680 | self.g_code.insert(END, "XZ" ) 681 | flist.append( "(XZ carriage)\n") 682 | #z = 0 - self.rootymin 683 | flist.append( "(trailing edge will be AT LEAST %0.1f%s above bottom of panel)\n" % (self.sp, self.units)) 684 | #if (self.trail > 0): 685 | # flist.append( "(Trailing edge limit %0.2f%s)\n" % (self.trail, self.units)) 686 | if (self.unit): 687 | flist.append( "G20\n"); # inch mode 688 | prec = '%0.4f' # thous/10 for inch mode 689 | retract = -0.25 690 | else: 691 | flist.append( "G21\n") # metric mode 692 | prec = '%0.3f' # 1/1000 mm for metricmode 693 | retract = -5.0 694 | 695 | if (self.xyuv == 0): 696 | flist.append( "G90\n") 697 | if self.grblmode == True: 698 | flist.append("G49 \n") 699 | else: 700 | flist.append("G49 G64 P0.01\n") #G64 not implemented in GBRL 701 | else: 702 | flist.append( "G90 G49\n") 703 | 704 | 705 | if self.debug: print("prec " + str(prec)) 706 | # you will need to add in any other header codes you need, here 707 | 708 | if self.debug: 709 | print(retract) 710 | print(sp) 711 | print(self.feedrate) 712 | if (self.xyuv == 0): 713 | if self.grblmode: 714 | fmt = "G00 X"+prec+" Y"+prec+" U"+prec+" Z"+prec+" F%0.1f\n" 715 | else: 716 | fmt = "G00 X"+prec+" Y"+prec+" U"+prec+" V"+prec+" F%0.1f\n" 717 | else: 718 | if (self.xyuv == 1): # YZ 719 | fmt = "G00 Y"+prec+" Z"+prec+" F%0.1f\n" 720 | else: #XZ 721 | fmt = "G00 X"+prec+" Z"+prec+" F%0.1f\n" 722 | #start heights, sp + offset of first point on top surface 723 | if flag and (invert == -1): 724 | #doing an upside down profile, do lower side first, ie run loop backwards 725 | start = len(p1) - 1 726 | else: 727 | start = 0 728 | if (self.xy): # XY gantry on left 729 | if (direc == 'right'): # means second profile is smaller, 730 | shXY = sp + p1[start][1] 731 | shUV = sp + p2[start][1] 732 | else: 733 | shXY = sp + p2[start][1] 734 | shUV = sp + p1[start][1] 735 | else: # XY gantry on right 736 | if (direc == 'left'): # means second profile is smaller, 737 | shXY = sp + p1[start][1] 738 | shUV = sp + p2[start][1] 739 | else: 740 | shXY = sp + p2[start][1] 741 | shUV = sp + p1[start][1] 742 | flist.append("(seek to start height)\n") 743 | if (self.xyuv == 0): 744 | flist.append( fmt % (retract,shXY,retract,shUV,self.feedrate)) # EMC bleats if no feed speed on first G0 instructions 745 | else: 746 | flist.append( fmt % (retract,shXY,self.feedrate)) 747 | spt = [0,shXY,0,shUV] 748 | # do skins and then cut, assume wire 0,0 on surface of platten 749 | self.g_code.insert(END, " doing '%s' with Foamthick %.3f%s\n" % (direc,self.foamthickness, self.units)) 750 | """ 751 | if (($skintop > 0) || ($skinbot > 0) && ( ($foamthick >0) && ($foamchord > 0) )) 752 | { 753 | fprintf($of, "(skinning $skintop $skinbot on block $wingspan x $foamchord x $foamthick)\n"); 754 | $top = $foamthick - $skintop; 755 | fprintf($of,"G00 Y%0.2f V%0.2f\n",$top,$top); 756 | $fc = $foamchord + 5; 757 | fprintf($of,"G01 X%0.1f U%0.1f\n",$fc,$fc); 758 | fprintf($of,"G00 Y%0.2f V%0.2f\n",$skinbot,$skinbot); 759 | fprintf($of,"G01 X-5 U-5\n"); 760 | fprintf($of,"G00 Y%0.2f V%0.2f\n",$sp,$sp); 761 | } 762 | else 763 | fprintf($of, "(no skins)\n"); 764 | """ 765 | # seek to start point 766 | # must create the format string before using it 767 | if (self.xyuv == 0): 768 | if self.grblmode: 769 | fmt = "G01 X"+prec+" Y"+prec+" U"+prec+" Z"+prec+"\n" 770 | else: 771 | fmt = "G01 X"+prec+" Y"+prec+" U"+prec+" V"+prec+"\n" 772 | else: 773 | if (self.xyuv == 1): 774 | fmt = "G01 Y"+prec+" Z"+prec+"\n" 775 | else: 776 | fmt = "G01 X"+prec+" Z"+prec+"\n" 777 | if (self.xyuv != 0) and (self.toffset != 0): 778 | flist.append("(Need a Straight wing please)\n") 779 | self.g_code.insert(END, " Straight wings only! aborting") 780 | return 781 | #else: 782 | #flist.append("(do we need a seek to trailing edge here?)\n") 783 | 784 | if (self.toffset > 0): 785 | flist.append("(seek to trailing edge)\n") 786 | if (self.xy): # XY gantry on left 787 | if (direc == 'right'): # means second profile is smaller, 788 | spt = [0,shXY, self.toffset, shUV] 789 | flist.append( fmt % (0,shXY, self.toffset, shUV)) 790 | else: 791 | spt = [self.toffset,shXY,0,shUV] 792 | flist.append( fmt % (self.toffset,shXY,0,shUV)) # first profile smaller 793 | else: # XY gantry on right 794 | if (direc == 'left'): # means second profile is smaller, 795 | spt = [0,shXY,self.toffset,shUV] 796 | flist.append( fmt % (0,shXY,self.toffset,shUV)) 797 | else: 798 | spt = [ self.toffset,shXY,0,shUV] 799 | flist.append( fmt % (self.toffset,shXY,0,shUV)) # first profile smaller 800 | if (self.toffset < 0): 801 | flist.append("(seek to trailing edge, swept)\n" ) 802 | if (self.xy): # XY gantry on left 803 | if (direc == 'right'): # means second profile is smaller, 804 | spt = [-self.toffset, shXY,0,shUV] 805 | flist.append( fmt % (-self.toffset, shXY,0,shUV)) 806 | else: 807 | spt = [ 0,shXY,-self.toffset,shUV] 808 | flist.append( fmt % (0,shXY,-self.toffset,shUV)) # first profile smaller 809 | else: # XY gantry on right 810 | if (direc == 'left'): # means second profile is smaller, 811 | spt = [-self.toffset,shXY,0,shUV] 812 | flist.append( fmt % (-self.toffset,shXY,0,shUV)) 813 | else: 814 | spt =[0,shXY,-self.toffset,shUV] 815 | flist.append( fmt % (0,shXY,-self.toffset,shUV)) # first profile smaller 816 | 817 | #$xoffset = ($toffset < 0) ? -$toffset : 0; 818 | #$uoffset = ($toffset < 0) ? 0 : $toffset ; 819 | 820 | if (self.toffset < 0): 821 | xoffset = -self.toffset 822 | uoffset = 0 823 | else: 824 | xoffset = 0 825 | uoffset = self.toffset 826 | 827 | if flag and (invert == -1): 828 | #doing an upside down profile, do lower side first, ie run loop backwards 829 | start = len(p1) - 1 830 | end = -1 831 | step = -1 832 | else: 833 | #do top surface first as normal 834 | start = 0 835 | end = len(p1) 836 | step = 1 837 | #print "%d %d %d" %(start,end,step) 838 | #print len(p1), len(p2) 839 | flist.append("(do profile)\n" ) 840 | for idx in range(start, end, step): 841 | #print "idx %d" % idx 842 | if (self.xyuv == 0): 843 | if (self.xy): # XY gantry on left 844 | if (direc == 'right'): 845 | flist.append(fmt % (p1[idx][0] + xoffset, sp + p1[idx][1] * invert, p2[idx][0] + uoffset, sp + p2[idx][1] * invert)) 846 | else: 847 | flist.append(fmt % (p1[idx][0] + uoffset, sp + p1[idx][1] * invert, p2[idx][0] + xoffset, sp + p2[idx][1] * invert)) 848 | else: # XY gantry on right 849 | if (direc == 'left'): 850 | flist.append(fmt % (p1[idx][0] + xoffset, sp + p1[idx][1] * invert, p2[idx][0] + uoffset, sp + p2[idx][1] * invert)) 851 | else: 852 | flist.append(fmt % (p1[idx][0] + uoffset, sp + p1[idx][1] * invert, p2[idx][0] + xoffset, sp + p2[idx][1] * invert)) 853 | else: 854 | #self.g_code.insert(END, fmt) 855 | flist.append(fmt % (p1[idx][0] + xoffset, sp + p1[idx][1] * invert )) 856 | #end idx loop 857 | 858 | #close the trailing edge by going back to start point 859 | flist.append("(close trailing edge)\n" ) 860 | if (self.xyuv == 0): 861 | flist.append( fmt % (spt[0], spt[1], spt[2], spt[3]) ) 862 | else: 863 | flist.append( fmt % (spt[0], spt[1]) ) 864 | if (self.xyuv != 0): 865 | flist.append("G4 P0.075\n") 866 | if self.unit: 867 | retract = -0.25 868 | else: 869 | retract = -5 870 | #retract 871 | flist.append("(retract out of foam)\n" ) 872 | if (self.xyuv == 0): 873 | flist.append( fmt % (retract,shXY,retract,shUV)) 874 | else: 875 | flist.append( fmt % (retract*2,shXY)) 876 | flist.append("G4 P0.075\n") 877 | if ( not(flag) or ( (invert == -1) and flag)): 878 | if (self.xyuv == 0): 879 | if self.grblmode: 880 | fmt0 = "G00 X"+prec+" Y"+prec+" U"+prec+" Z"+prec+"\n" 881 | else: 882 | fmt0 = "G00 X"+prec+" Y"+prec+" U"+prec+" V"+prec+"\n" 883 | flist.append( fmt0 % (2*retract,-2*retract,2*retract,-2*retract)) 884 | else: 885 | if (self.xyuv == 1): 886 | fmt0 = "G00 Y"+prec+" Z"+prec+"\n" 887 | else: 888 | fmt0 = "G00 X"+prec+" Z"+prec+"\n" 889 | flist.append( fmt0 % (2*retract,-2*retract)) 890 | if (flag): 891 | if (invert == -1): 892 | flist.append("M5\nM30\n") 893 | flist.append("%\n") 894 | else: 895 | flist.append("M5\nM30\n") 896 | flist.append("%\n") 897 | #end of plot() 898 | status_bar_update("G-Code Generated") 899 | 900 | def WriteLeftToAxis(self): 901 | for line in self.g_code_left: 902 | sys.stdout.write(line) 903 | self.quit() 904 | 905 | def WriteRightToAxis(self): 906 | for line in self.g_code_right: 907 | sys.stdout.write(line) 908 | self.quit() 909 | 910 | def WriteBothToAxis(self): 911 | if len(self.g_code_both) > 100: 912 | for line in self.g_code_both: 913 | sys.stdout.write(line) 914 | self.quit() 915 | else: 916 | self.g_code.insert(END,'ERROR: no BOTH data to write') 917 | 918 | def SaveModel(self): 919 | """ save an ini file like this 920 | [drunk] 921 | wingspan=770 922 | root=340 923 | tip=150 924 | trail=1 925 | sweep=50 926 | gantry=900 927 | rootfile=e374.dat 928 | tipfile=e374.dat 929 | foamchord=410 930 | foamthick=50 931 | feedspeed=345 932 | xy=0 933 | inch=0 934 | """ 935 | 936 | try: 937 | """ save in ini in the NC folder """ 938 | self.NcFileDirectory = self.GetIniData(self.inifile,'Directories','NcFiles') 939 | except: 940 | tkinter.messagebox.showinfo('Missing INI Data', 'You must set the\n' \ 941 | 'NC File Directory\n' \ 942 | 'before saving a file.\n' \ 943 | 'Go to Edit/NC Directory\n' \ 944 | 'in the menu to set this option') 945 | return 946 | 947 | modelname = self.ModelNameVar.get() 948 | modelname = modelname.strip() 949 | if (modelname == ''): 950 | tkinter.messagebox.showinfo('Need a model name in order to save a model') 951 | return 952 | 953 | inifile = self.NcFileDirectory +'/'+ modelname + '.ini' 954 | 955 | config = configparser.ConfigParser() 956 | 957 | # When adding sections or items, add them in the reverse order of 958 | # how you want them to be displayed in the actual file. 959 | # In addition, please note that using RawConfigParser's and the raw 960 | # mode of ConfigParser's respective set functions, you can assign 961 | # non-string values to keys internally, but will receive an error 962 | # when attempting to write to a file or when you get it in non-raw 963 | # mode. ConfigParser does not allow such assignments to take place. 964 | config.add_section(modelname) 965 | config.set(modelname, 'wingspan', self.WingSpanVar.get()) 966 | config.set(modelname, 'washout', self.WashoutVar.get()) 967 | config.set(modelname, 'root', self.RootChordVar.get()) 968 | config.set(modelname, 'tip' , self.TipChordVar.get()) 969 | items = self.RootProfilelistbox.curselection() 970 | item = self.RootProfilelistbox.get(items[0]) 971 | config.set(modelname, 'rootfile', item) 972 | items = self.TipProfilelistbox.curselection() 973 | item = self.TipProfilelistbox.get(items[0]) 974 | config.set(modelname, 'tipfile', item) 975 | config.set(modelname, 'foamchord', self.FoamChordVar.get()) 976 | config.set(modelname, 'foamthick', self.FoamThicknessVar.get()) 977 | config.set(modelname, 'trail', self.TrailingEdgeLimitVar.get()) 978 | config.set(modelname, 'sweep', self.LeadingEdgeSweepVar.get()) 979 | config.set(modelname, 'gantry', self.GantryLengthVar.get()) 980 | config.set(modelname, 'feedspeed', self.FeedrateVar.get()) 981 | xy = self.XYsideVar.get() 982 | config.set(modelname, 'xy', str(xy)) 983 | unit = self.UnitVar.get() 984 | config.set(modelname, 'inch', str(unit)) 985 | 986 | # Writing our configuration file to 'example.cfg' 987 | with open(inifile, 'w') as configfile: 988 | config.write(configfile) 989 | self.g_code.insert(END, 'Saved model\n') 990 | self.WriteIniData(self.inifile,'autoload','model',modelname) #write for autoload 991 | status_bar_update("Model Settings Saved as " + modelname) 992 | 993 | def ReadModel(self, filename): 994 | config = configparser.ConfigParser() 995 | config.read(filename) 996 | #get the model name from the filename 997 | modelname = os.path.splitext(os.path.basename(filename))[0] 998 | if (config.has_section(modelname)): 999 | self.ModelNameVar.set(modelname) 1000 | self.WingSpanVar.set( config.get(modelname, 'wingspan')) 1001 | try: 1002 | self.WashoutVar.set( config.get(modelname, 'washout')) 1003 | except: 1004 | self.WashoutVar.set('0') 1005 | self.RootChordVar.set(config.get(modelname, 'root')) 1006 | self.TipChordVar.set( config.get(modelname, 'tip' )) 1007 | 1008 | item = config.get(modelname, 'rootfile') 1009 | item = os.path.join(self.DatDir, item) 1010 | last = len(self.profiles) - 1 1011 | self.RootProfilelistbox.selection_clear(0, last) 1012 | self.RootProfilelistbox.selection_set(self.profiles.index(item)) 1013 | 1014 | item = config.get(modelname, 'tipfile') 1015 | item = os.path.join(self.DatDir, item ) 1016 | self.TipProfilelistbox.selection_clear(0, last) 1017 | self.TipProfilelistbox.selection_set(self.profiles.index(item)) 1018 | 1019 | self.FoamChordVar.set( config.get(modelname, 'foamchord')) 1020 | self.FoamThicknessVar.set( config.get(modelname, 'foamthick')) 1021 | self.TrailingEdgeLimitVar.set( config.get(modelname, 'trail')) 1022 | self.LeadingEdgeSweepVar.set( config.get(modelname, 'sweep')) 1023 | self.GantryLengthVar.set( config.get(modelname, 'gantry')) 1024 | self.FeedrateVar.set( config.get(modelname, 'feedspeed')) 1025 | self.XYsideVar.set( config.getint(modelname, 'xy')) 1026 | self.UnitVar.set( config.getint(modelname, 'inch')) 1027 | self.g_code.insert(END, 'loaded model ' + modelname + "\n") 1028 | self.modelname = modelname 1029 | return 1 1030 | else: 1031 | return 0 1032 | 1033 | def LoadModel(self): 1034 | try: 1035 | """ save in ini in the NC folder """ 1036 | self.NcFileDirectory = self.GetIniData(self.inifile,'Directories','NcFiles') 1037 | except: 1038 | tkinter.messagebox.showinfo('Missing INI Data', 'You must set the\n' \ 1039 | 'NC File Directory\n' \ 1040 | 'before saving a file.\n' \ 1041 | 'Go to Edit/NC Directory\n' \ 1042 | 'in the menu to set this option') 1043 | return 1044 | filename = askopenfilename(initialdir=self.NcFileDirectory,defaultextension='.ini',filetypes=[('INI','*.ini')]) 1045 | if self.ReadModel(filename): 1046 | #write this modelname to the ini file for autoload at startup 1047 | self.WriteIniData(self.inifile,'autoload','model',self.modelname) 1048 | 1049 | 1050 | """ 1051 | def WriteToAxis(self): 1052 | sys.stdout.write(self.g_code.get(0.0, END)) 1053 | self.quit() 1054 | """ 1055 | 1056 | #what code is this? 1057 | def FToD(self,s): # Float To Decimal 1058 | """ 1059 | Returns a decimal with 4 place precision 1060 | valid imputs are any fraction, whole number space fraction 1061 | or decimal string. The input must be a string! 1062 | """ 1063 | s = s.strip(' ') # remove any leading and trailing spaces 1064 | if s == '': # make sure it does not crash on empty string 1065 | s = '0' 1066 | D=Decimal # Save typing 1067 | P=D('0.000001') # Set the precision wanted 1068 | if ' ' in s: # if it is a whole number with a fraction 1069 | w,f=s.split(' ',1) 1070 | w=w.strip(' ') # make sure there are no extra spaces 1071 | f=f.strip(' ') 1072 | n,d=f.split('/',1) 1073 | ret = D(D(n)/D(d)+D(w)).quantize(P) 1074 | return float(ret) 1075 | elif '/' in s: # if it is just a fraction 1076 | n,d=s.split('/',1) 1077 | ret = D(D(n)/D(d)).quantize(P) 1078 | return float(ret) 1079 | ret = D(s).quantize(P) # if it is a decimal number already 1080 | return float(ret) 1081 | 1082 | def GetIniData(self,FileName,SectionName,OptionName): 1083 | """ 1084 | Returns the data in the file, section, option if it exists 1085 | of an .ini type file created with ConfigParser.write() 1086 | If the file is not found or a section or an option is not found 1087 | returns an exception 1088 | """ 1089 | self.cp = configparser.ConfigParser() 1090 | try: 1091 | self.cp.read(FileName) 1092 | try: 1093 | self.cp.has_section(SectionName) 1094 | try: 1095 | IniData=self.cp.get(SectionName,OptionName) 1096 | except configparser.NoOptionError: 1097 | raise Exception('NoOptionError') 1098 | except configparser.NoSectionError: 1099 | raise Exception('NoSectionError') 1100 | except IOError: 1101 | raise Exception('NoFileError') 1102 | return IniData 1103 | 1104 | def WriteIniData(self,FileName,SectionName,OptionName,OptionData): 1105 | """ 1106 | Pass the file name, section name, option name and option data 1107 | When complete returns 'sucess' 1108 | """ 1109 | self.cp = configparser.ConfigParser() 1110 | self.cp.read(FileName) # read existing stuff and add to it 1111 | if not self.cp.has_section(SectionName): 1112 | self.cp.add_section(SectionName) 1113 | self.cp.set(SectionName,OptionName,OptionData) 1114 | with open(FileName, 'w') as configfile: 1115 | self.cp.write(configfile) 1116 | 1117 | 1118 | def GetDirectory(self): 1119 | self.DirName = askdirectory(initialdir='/home',title='Please select a directory') 1120 | if len(self.DirName) > 0: 1121 | return self.DirName 1122 | 1123 | def CopyClpBd(self): 1124 | self.g_code.clipboard_clear() 1125 | self.g_code.clipboard_append(self.g_code.get(0.0, END)) 1126 | 1127 | def WriteToFile(self): 1128 | try: 1129 | self.NcFileDirectory = self.GetIniData(self.inifile,'Directories','NcFiles') 1130 | except: 1131 | tkinter.messagebox.showinfo('Missing INI', 'You must set the\n' \ 1132 | 'G-code File Directory\n' \ 1133 | 'before saving a file.\n' \ 1134 | 'Go to Edit/G-code Directory\n' \ 1135 | 'in the menu to set this option') 1136 | # try: 1137 | # os.path.join(self.NcFileDirectory, self.modelname, '-right.nc') 1138 | # os.path.join(self.NcFileDirectory, self.modelname, '-left.nc') 1139 | # os.path.join(self.NcFileDirectory, self.modelname,'-both.nc') 1140 | if (self.xyuv == 0 ): 1141 | fname = os.path.join(self.NcFileDirectory, self.modelname+ '-right' + self.ext) 1142 | of = open(fname,'w') 1143 | for line in self.g_code_right: 1144 | of.write(line) 1145 | of.close() 1146 | self.g_code.insert(END, 'Right file written ' + fname + '\n') 1147 | right_file= os.path.basename(fname) 1148 | 1149 | 1150 | fname = os.path.join(self.NcFileDirectory, self.modelname + '-left' + self.ext) 1151 | of = open(fname,'w') 1152 | for line in self.g_code_left: 1153 | of.write(line) 1154 | of.close() 1155 | self.g_code.insert(END, 'Left file written ' + fname + '\n') 1156 | left_file= os.path.basename(fname) 1157 | status_bar_update("G-Code Files Created [" + left_file + "] [" + right_file +"]") 1158 | 1159 | both = os.path.join(self.NcFileDirectory, self.modelname + '-both' + self.ext) 1160 | if self.need == 0: 1161 | of = open(both,'w') 1162 | for line in self.g_code_both: 1163 | of.write(line) 1164 | of.close() 1165 | self.g_code.insert(END, 'Both file written ' + both + '\n') 1166 | else: 1167 | if os.path.exists(both): 1168 | os.unlink(both) 1169 | else: # write one file for cartesian cutter 1170 | if (self.xyuv == 1 ): 1171 | tag = 'YZ' 1172 | else: 1173 | tag = 'XZ' 1174 | fname = os.path.join(self.NcFileDirectory, self.modelname + '-'+tag + self.ext) 1175 | of = open(fname,'w') 1176 | for line in self.g_code_left: 1177 | of.write(line) 1178 | of.close() 1179 | self.g_code.insert(END, 'Cartesian written ' + fname + '\n') 1180 | 1181 | #self.NewFileName = asksaveasfile(initialdir=self.NcFileDirectory,mode='w', master=self.master,title='Create NC File',defaultextension='.ngc') 1182 | #self.NewFileName.write(self.g_code.get(0.0, END)) 1183 | #self.NewFileName.close() 1184 | # except: 1185 | # tkMessageBox.showinfo('broken','something broke while writing files\n') 1186 | 1187 | def NcFileDirectory(self): 1188 | DirName = self.GetDirectory() 1189 | if len(DirName) > 0: 1190 | self.WriteIniData(self.inifile,'Directories','NcFiles',DirName) 1191 | 1192 | # this is the folder where we find our .dat files for foil shapes 1193 | def DatFileDirectory(self): 1194 | DirName = self.GetDirectory() 1195 | if len(DirName) > 0: 1196 | self.WriteIniData(self.inifile,'Directories','DatFiles',DirName) 1197 | 1198 | def Simple(self): 1199 | tkinter.messagebox.showinfo('Feature', 'Sorry this Feature has\nnot been programmed yet.') 1200 | 1201 | def ClearTextBox(self): 1202 | self.g_code.delete(1.0,END) 1203 | 1204 | def SelectAllText(self): 1205 | self.g_code.tag_add(SEL, '1.0', END) 1206 | 1207 | def SelectCopy(self): 1208 | self.SelectAllText() 1209 | self.CopyClpBd() 1210 | 1211 | def HelpAbout(self): 1212 | tkinter.messagebox.showinfo('Help About', 'Programmed by ' 1213 | 'the Swarfer\n\nrcKeith conversion to Python3\n\nand GRBL compatibilty\n\n' 1214 | 'Version 2.0') 1215 | 1216 | # take an array read from a dat file and strip out all comments so we only have the co-ord numbers on return 1217 | def stripfile(self, thefile): 1218 | done = 0 1219 | while not(done): 1220 | done = 1 1221 | bits = 0 1222 | for key in range(0, len(thefile)-1): 1223 | line = thefile[key] 1224 | bits = str.strip(line) 1225 | bits = str.split(bits) #(preg_split('/[ ]+|\t/',trim(self.line)); 1226 | for idx in range(0, len(bits)-1): # prevent losing lines like '0 0' 1227 | if (bits[idx] == '0'): 1228 | bits[idx] = '0.0' 1229 | if (len(bits) == 3) and (re.search('[a-d]|[f-z]', line) == None ): # some files have 3 fields, remove first one, the line number 1230 | bits.remove( bits[0]) 1231 | if ( re.search('[a-d]|[f-z]|[A-D]|[F-Z]',line) != None ): 1232 | thefile.remove(line) 1233 | done = 0 1234 | break 1235 | if ( line == ''): 1236 | thefile.remove(line) 1237 | done = 0 1238 | break 1239 | thefile[key] = str.strip(line) 1240 | return thefile 1241 | 1242 | #creates the self.root array of cordinates 1243 | def FindThicknessesRoot(self): 1244 | idx = 0 1245 | self.tipymax = self.rootymax = 0 1246 | self.tipymin = self.rootymin = 10000 1247 | self.idxl = len(self.rootprofile) / 4 # trailing edge limiting index limit 1248 | self.actualrootthickmax = 0 # find the actual root thickness max 1249 | self.actualrootthickmin = 10000 # find the actual root thickness min 1250 | 1251 | self.root = list() 1252 | div = 0.0 # if first value is not 1.0 then set this so that all values can be scaled to 1 1253 | 1254 | for line in self.rootprofile: 1255 | bits = line.split() 1256 | if len(bits): 1257 | 1258 | #scaling, some dat files are 0..1 and some are 0..100 1259 | if div == 0.0: 1260 | div = float(bits[0]) 1261 | 1262 | x = 1 - float(bits[0]) / div 1263 | y = float(bits[1]) / div 1264 | self.root.append( [0,0] ) 1265 | self.root[idx][0] = x * self.rootlength 1266 | self.root[idx][1] = y * self.rootlength 1267 | # want to know the actual root profile thickness to crosscheck against foamthick 1268 | art = y * self.rootchord 1269 | if self.actualrootthickmax < art: 1270 | self.actualrootthickmax = art 1271 | if self.actualrootthickmin > art: 1272 | self.actualrootthickmin = art 1273 | 1274 | if self.rootymax < self.root[idx][1]: 1275 | self.rootymax = self.root[idx][1] 1276 | if self.rootymin > self.root[idx][1]: 1277 | self.rootymin = self.root[idx][1] 1278 | 1279 | idx = idx + 1 1280 | 1281 | self.actualrootthick = self.actualrootthickmax - self.actualrootthickmin; 1282 | #end FindThicknessRoot 1283 | 1284 | # creates the self.tip array of coordinates 1285 | def CreateTip(self): 1286 | idx = 0 1287 | self.tgtravel = 0 # tip gantry travel, essentially tip length at gantry 1288 | self.tip = list() 1289 | div = -1 1290 | for line in self.tipprofile: 1291 | bits = line.split() 1292 | if len(bits): 1293 | if div == -1: 1294 | div = float(bits[0]) 1295 | x = 1 - float(bits[0]) / div #bits[0] is x 1296 | y = float(bits[1]) / div #bits[1] is y 1297 | self.tip.append( [0,0] ) 1298 | self.tip[idx][0] = x * self.tiplength 1299 | self.tip[idx][1] = y * self.tiplength 1300 | 1301 | idx = idx + 1 1302 | #end CreateTip 1303 | 1304 | #find the thicknesses for tip, do this after rotate!, coords are in mm 1305 | def FindThicknessesTip(self): 1306 | idx = 0 1307 | self.tgtravel = 0 # tip gantry travel, essentially tip length at gantry 1308 | self.tipymax = 0 1309 | self.tipymin = 10000 1310 | 1311 | for tip in self.tip: 1312 | x = tip[0] #is x 1313 | y = tip[1] #is y 1314 | # tip gantry travel - for drawing 1315 | if (x > self.tgtravel): 1316 | self.tgtravel = x 1317 | 1318 | if self.tipymax < y: 1319 | self.tipymax = y 1320 | if self.tipymin > y: 1321 | self.tipymin = y 1322 | 1323 | idx = idx + 1 1324 | #end FindThicknessTip 1325 | 1326 | def TrailingEdgeLimits1(self): 1327 | # do trailing edge limiting for both profiles 1328 | c = len(self.tip) -1 1329 | for idx in range(2, self.idxl): 1330 | other = c - idx; 1331 | dist = self.tip[idx][1] - self.tip[other][1] 1332 | if (dist < self.trail): 1333 | adjust = (self.trail - dist) / 2 1334 | self.tip[idx][1] += adjust 1335 | self.tip[other][1] -= adjust 1336 | 1337 | dist = self.root[idx][1] - self.root[other][1] 1338 | if (dist < self.trail): 1339 | adjust = (self.trail - dist) / 2; 1340 | self.root[idx][1] += adjust; 1341 | self.root[other][1] -= adjust; 1342 | #end TrailingEdgeLimits 1343 | 1344 | #new way, move the top to be trail above the bottom surface 1345 | def TrailingEdgeLimits2(self): 1346 | # do trailing edge limiting for both profiles 1347 | for this in range(0, round(self.idxl)): 1348 | other = len(self.root) - 1 - this # index of the bottom surface point 1349 | dist = self.root[this][1] - self.root[other][1] 1350 | if dist < self.trail: 1351 | self.root[this][1] = self.root[other][1] + self.trail 1352 | dist = self.tip[this][1] - self.tip[other][1] 1353 | if dist < self.trail: 1354 | self.tip[this][1] = self.tip[other][1] + self.trail 1355 | 1356 | #end TrailingEdgeLimits 1357 | 1358 | def FindStartPoints(self): 1359 | # do foam stuff and calc start height 1360 | # might need to adjust this to account for rotation of the tip profile putting its max/min beyond the root profile 1361 | if self.unit: 1362 | off = 0.25 1363 | else: 1364 | off = 6 #min offet from outside of foam 1365 | if (self.foamthickness == 0): 1366 | print("foamthick = 0") 1367 | self.foamthickness = self.rootymax - self.rootymin + off 1368 | self.g_code.insert(END, " foamthickness calculated at " + self.Format(self.foamthickness,2) + self.units) 1369 | self.sp = ((self.foamthickness - self.skintop - self.skinbot) * -self.rootymin / (self.rootymax - self.rootymin)) + self.skinbot 1370 | else: 1371 | if (self.actualrootthick > (self.foamthickness - self.skintop - self.skinbot) ): 1372 | print("too thick") 1373 | self.g_code.insert(END," ERROR: actualroothickness %.2f EXCEEDS foamthickness %.2f" % (self.actualrootthick, self.foamthickness)) 1374 | self.g_code.insert(END," Recalculating foamthickness\n") 1375 | self.foamthickness = round(self.actualrootthick + off,0) 1376 | self.skintop = self.skinbot = 0 1377 | self.sp = ((self.foamthickness - self.skintop - self.skinbot) * -self.rootymin / (self.rootymax - self.rootymin)) + self.skinbot 1378 | else: 1379 | # startpoint, calculate it to put wing centered in foamthick less skins 1380 | self.rt = self.rootymax - self.rootymin 1381 | self.g_code.insert(END, "\n root thickness %f%s\n foamthickness %f%s\n" % (self.actualrootthick,self.units, self.foamthickness,self.units)) 1382 | if (self.skintop or self.skinbot): 1383 | t = self.foamthickness - self.skintop - self.skinbot 1384 | self.g_code.insert(END," (%f after skinning)",(t)) 1385 | 1386 | #max height of profiles 1387 | rat = max(self.rootymax,self.tipymax) - min(self.rootymin, self.tipymin) 1388 | #max off set from bottom 1389 | h = -min(self.rootymin , self.tipymin) 1390 | #print "rat %.3f h %.3f" % (rat, h) 1391 | leftover = (self.foamthickness - self.skintop - self.skinbot) - rat 1392 | self.sp = leftover/2 + h + self.skinbot 1393 | 1394 | #now check that it still fits the foam 1395 | if ((self.sp + self.rootymax) > self.foamthickness) or (self.sp + self.tipymax > self.foamthickness): 1396 | self.g_code.insert(END, "ERROR: top of profile will exit the foam, need thicker foam!\n") 1397 | 1398 | # do some calcs for the BOTH output 1399 | self.need = 0 1400 | if ( (rat*2+2*off) > (self.foamthickness - self.skintop - self.skinbot) ) : 1401 | self.g_code.insert(END," WARNING: FOAMTHICK is not enough to cut BOTH panels\n") 1402 | self.need = rat*2 + off*2 1403 | self.g_code.insert(END, " WARNING: need foam at least %0.2f%s thick\n" % (self.need, self.units) ) 1404 | self.fth = (self.foamthickness / 2) 1405 | 1406 | self.spl = self.fth + ( (self.fth-self.skintop) - rat)/2 + h # left start point in top half 1407 | self.spr = self.fth - ( (self.fth-self.skinbot) - rat)/2 - h # right start point in bottom half 1408 | 1409 | bottomoftop = self.spl + min(self.rootymin, self.tipymin) 1410 | topofbottom = self.spr - min(self.rootymin, self.tipymin) 1411 | #print "spl %.3f spr %.3f bottomoftop %.3f topofbottom %.3f" % (self.spl, self.spr, bottomoftop, topofbottom) 1412 | 1413 | if (bottomoftop < topofbottom): 1414 | self.g_code.insert(END, "ERROR: sections are colliding in BOTH, need thicker foam by %.3f \n" % (topofbottom - bottomoftop)) 1415 | 1416 | #end of FindStartPoints 1417 | 1418 | #RESAMPLE stuff 1419 | 1420 | #calculate new y for this $x, from the points on the line x1,y1 to x2,y2 1421 | def newy(self, x1,y1,x2,y2,x): 1422 | m = (y2-y1) / (x2-x1) 1423 | c = y1 - m * x1 1424 | y = m * x + c 1425 | return y 1426 | 1427 | #resample sla to have the same number of points as master 1428 | #master must have more points than slave 1429 | # the two lists are the raw lines from the dat file 1430 | def resample(self, mas,sla): 1431 | #mmin point in master 1432 | mmin = 1000 1433 | prec = 7 1434 | idx = 0 1435 | midx = -1 1436 | mdiv = -1 1437 | for m in mas: 1438 | bits = m.split() 1439 | if mdiv < 0: 1440 | mdiv = float(bits[0]) 1441 | x = float(bits[0]) / mdiv # must scale all values 1442 | y = float(bits[1]) / mdiv 1443 | 1444 | if x < mmin: 1445 | midx = idx 1446 | mmin = x 1447 | idx = idx + 1 1448 | #print("mmin %0.7s midx %d mdiv %0.7f" % (mmin,midx,mdiv)) 1449 | #find smin point in sla 1450 | idx = 0 1451 | smin = 1000 1452 | sdiv = -1 1453 | for s in sla: 1454 | bits = s.split() 1455 | if sdiv < 0 : 1456 | sdiv = float(bits[0]) 1457 | x = float(bits[0]) / sdiv 1458 | y = float(bits[1]) / sdiv 1459 | if x < smin: 1460 | smin = x 1461 | sidx = idx 1462 | idx = idx + 1 1463 | #print("smin %0.7s sidx %d sdiv %0.7f mdiv %0.7f" % (smin,sidx,sdiv,mdiv)) 1464 | 1465 | new = list() 1466 | top = 1 1467 | idx = 0 1468 | for m in mas: 1469 | #if top: print "%d %s %d" %(idx,m, top) 1470 | bits = m.split() 1471 | cx = float(bits[0]) / mdiv 1472 | cy = float(bits[1]) / mdiv 1473 | #print "cx %.7f top %d" % (cx,top) 1474 | if (cx < (mmin + 0.00000001)): 1475 | top = 0 1476 | #print str(cx) + " change to bottom" + str(top) + " idx = " + str(idx) + "\n" 1477 | if (top == 1): 1478 | for i in range(0,sidx+1): 1479 | bits = sla[i].split() 1480 | sx = float(bits[0]) / sdiv 1481 | sy = float(bits[1]) / sdiv 1482 | 1483 | if abs(cx - sx) < 0.000001: 1484 | #print "%d cx=sx appending sla %f,%f" % (idx,sx,sy) 1485 | new.append("%0.7f %0.7f" % (sx,sy)) # echo "top equal\n"; 1486 | break 1487 | 1488 | bits1 = sla[i+1].split() #split next line 1489 | sx1 = float(bits1[0]) / sdiv 1490 | sy1 = float(bits1[1]) / sdiv 1491 | 1492 | if mmin < smin: 1493 | if (abs(cx - mmin) < 0.001): 1494 | #print "sx %.7f smin %.7f" % (sx,smin) 1495 | if (abs(sx - smin) < 0.001): 1496 | #print " sx == smin" 1497 | new.append("%0.7f %0.7f" % (cx , sy)) 1498 | break 1499 | if (cx > sx1): 1500 | if abs(sx - sx1) < 0.000001: 1501 | #print "%d sx = sx1 appending sla %f,%f" % (idx,cx,sy) 1502 | new.append("%0.7f %0.7f" % (cx , sy)) 1503 | else: 1504 | # calculate a new Y at this X 1505 | y = self.newy(sx1,sy1, sx, sy, cx ) 1506 | #print "%d calc new Y at %f = %f,%f " %(idx, cx,cx,y) 1507 | new.append("%0.7f %0.7f" % (cx, y)) # echo "top new $cx $y\n"; 1508 | break 1509 | if top == 0: # echo "bottom "; 1510 | for i in range(sidx, len(sla)-1): 1511 | #print "i %d" % i 1512 | bits = sla[i].split() 1513 | sx = float(bits[0]) / sdiv 1514 | sy = float(bits[1]) / sdiv 1515 | if abs(cx - sx) < 0.000001: 1516 | #print "%d CX=SX appending sla %f,%f" % (idx,sx,sy) 1517 | new.append("%0.7f %0.7f" % (sx,sy)) # echo "top equal\n"; 1518 | break 1519 | 1520 | bits1 = sla[i+1].split() #split next line 1521 | sx1 = float(bits1[0]) / sdiv 1522 | sy1 = float(bits1[1]) / sdiv 1523 | 1524 | if (cx < sx1): 1525 | if abs(sx - sx1) < 0.000001: 1526 | #print "%d SX=SX1 %.7f,%.7f appending sla %f,%f" % (idx,sx,sx1, sx,sy) 1527 | new.append("%0.7f %0.7f" % (cx , sy)) 1528 | else: 1529 | # calculate a new Y at this X 1530 | #print "%d sx %.6f sx1 %.6f" % (idx,sx,sx1) 1531 | y = self.newy(sx,sy, sx1, sy1, cx ) 1532 | #print "%d CALC new Y at %f = %f,%f " %(idx, cx,cx,y) 1533 | new.append("%0.7f %0.7f" % (cx , y)) 1534 | break 1535 | idx = idx + 1 1536 | # now add last one 1537 | #print new 1538 | bits = sla[ len(sla)-1].split() 1539 | sx = float(bits[0]) / sdiv 1540 | sy = float(bits[1]) / sdiv 1541 | #print "%d CX=sx appending sla %f,%f" % (idx,sx,sy) 1542 | new.append("%0.7f %0.7f" % (sx,sy)) 1543 | #print len(mas), len(sla), len(new) 1544 | return new 1545 | #end resample 1546 | 1547 | #ROTATE 1548 | #from the web https://stackoverflow.com/questions/20023209/function-for-rotating-2d-objects 1549 | def rotatePolygon(self,polygon,theta): 1550 | """Rotates the given polygon which consists of corners represented as (x,y), 1551 | around the ORIGIN, clock-wise, theta degrees""" 1552 | theta = radians(theta) 1553 | rotatedPolygon = list() 1554 | for corner in polygon : 1555 | rotatedPolygon.append([ corner[0]*cos(theta)-corner[1]*sin(theta) , corner[0]*sin(theta)+corner[1]*cos(theta)] ) 1556 | return rotatedPolygon 1557 | 1558 | ### 1559 | 1560 | 1561 | app = Application() 1562 | app.master.title('Wing G-Code Generator 2.00') 1563 | # only load icon if file exists - still want it simple to install, one file download 1564 | if os.path.exists('f18.ico'): 1565 | app.master.iconbitmap('f18.ico') 1566 | 1567 | # Status Bar 1568 | status_bar = Label(app.master, text="Ready ", anchor=E,) 1569 | status_bar.pack(fill=X, side=BOTTOM, ipady=5) 1570 | 1571 | def status_bar_update(status_mgs): 1572 | status_bar.config(text = status_mgs + " ",foreground ="Blue") 1573 | return 1574 | 1575 | 1576 | 1577 | app.mainloop() 1578 | --------------------------------------------------------------------------------