├── .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 |
--------------------------------------------------------------------------------