# HG changeset patch # User mbayer # Date 1464938167 -7200 # Node ID 0bbb006204fc48ea018523d13b257b72a52cfb45 # Parent 51bf56ba3c109937869bd131971181a58ab2e750 Added printrun sourcecode from https://github.com/kliment/Printrun 03.06.2016 09:10 diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/.gitignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/.gitignore Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,10 @@ +*.pyc +.pronsolerc +*.swp +*.bak +uploads +.DS_Store +.vagrant +prontserve-env +printrun/gcoder_line.c +printrun/gcoder_line.so diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/COPYING --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/COPYING Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/NEWS.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/NEWS.md Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,31 @@ +printrun-20140406 +================= + +New features +------------ + +- New 3D visualization +- New GCode plater +- Updated "standard" controls +- New "mini" controls mode +- New print speed control slider in Pronterface +- New plater features: + * STL cutter + * STL rebase +- G-Code injection at beginning of layer and edition of entire G-Code +- The G-Code modified using the injector or layer editor can be saved +- Controls and log pane can be folded to leave more space for visualization +- Added a lighter GCode parser for non-graphical interfaces +- Window size and configuration is now saved across runs +- Power management: Printrun now runs on high priority during prints and should + inhibit sleep modes +- New host commands, `run_script` and `run_gcode_script` to run custom scripts + during prints. The output of the script ran by `run_gcode_script` will in + turn be processed as G-Code or host commands +- 3D viewer colors are now configurable + +Bugs fixed +---------- + +- Many fixes around custom buttons +- Much more :) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/README.cleanup --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/README.cleanup Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,36 @@ +Some cleanup commands: + +To add a space after each comma: +sed -e "s/\(\w\),\(\w\)/\1, \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\w\),\(\"\)/\1, \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\"\),\(\w\)/\1, \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\"\),\(\"\)/\1, \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\([)}\]]\),\(\w\)/\1, \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\([)}\]]\),\([\[{(]\)/\1, \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\w\),\([\[{(]\)/\1, \2/g" -i *.py printrun/*.py printrun/*/*.py + +To add spaces around each =: +sed -e "s/\(\w\)=\(\w\)/\1 = \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\w\)=\(\"\)/\1 = \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\w\)=\((\)/\1 = \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\w\)=\((\)/\1 = \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\w\)=\([\[{(]\)/\1 = \2/g" -i *.py printrun/*.py printrun/*/*.py + +To add spaces around each ==: +sed -e "s/\(\w\)==\(\w\)/\1 == \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\w\)==\(\"\)/\1 == \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\(\w\)==\((\)/\1 == \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\()\)==\(\w\)/\1 == \2/g" -i *.py printrun/*.py printrun/*/*.py +sed -e "s/\()\)==\((\)/\1 == \2/g" -i *.py printrun/*.py printrun/*/*.py + +Obviously this is not a perfect solution, it WILL break the code. Juste check the diff and fix what's wrong before commiting. + +Flake8 checking: +Flake8 can be used to check the coding style of the project. +The current source code (as of July 23rd 2013) has been checked using the following command: + flake8 . --statistics --count --ignore=E251,E701,E302,E501 --exclude=.svn,CVS,.bzr,.hg,.git,__pycache__,./printrun/cairosvg +This call ignores 4 kind of errors (E501: line being greater than 80 chars, +E701: multiple statements on one line (usually this is if ...: ...), E302: +wrong number of blank lines between functions, E251: unexpected spaces around +keywoard/parameter equals), the rest of the errors and warnings should be +killed as much as possible. Long lines should be avoided too. diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/README.i18n --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/README.i18n Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,47 @@ + +Printrun Internationalization + +Date: 06 August 2011 +Author: Jonathan Marsden + +Printrun is in the very early stages of being internationalized. + +The pronterface.py now uses gettext for the messages it generates. +The corresponding pronterface.pot file is at locale/pronterface.pot +and was generated using + + pygettext -o locale/pronterface.pot *.py printrun/*.py printrun/*/*.py + +followed by minor edits to the generated header. + +This template is the basis for all pronterface mesage catalogs. Right +now there is only one, for German. New ones can be created: + + # Create new pronterface message catalog for a different language + newlang="es" # use the correct code for your language + mkdir -p locale/${newlang}/LC_MESSAGES + cp locale/pronterface.pot locale/${newlanguage}/LC_MESSAGES/pronterface.po + cd locale/${newlanguage}/LC_MESSAGES/ + # Edit the .po file to add messages for newlang + msgfmt -o pronterface.mo pronterface.po + +To update a previously created message catalog from the template, use : + + msgmerge -U locale/fr/LC_MESSAGES/pronterface.po locale/pronterface.pot + +As currently coded, the default location for these message catalogs is + + /usr/share/pronterface/locale/ + +So, to install the catalogs, copy them to there: + + sudo cp -a locale /usr/share/pronterface/ + +To test pronterface in a new language, you can temporarily set LANG to +the language you are testing, for example + + LANG=de python pronterface.py + +Further automation for localization and packaging of Printrun would be +nice to see, but is not here yet. + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/README.md Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,477 @@ +Printrun consists of printcore, pronsole and pronterface, and a small collection of helpful scripts. + + * printcore.py is a library that makes writing reprap hosts easy + * pronsole.py is an interactive command-line host software with tabcompletion goodness + * pronterface.py is a graphical host software with the same functionality as pronsole + +# GETTING PRINTRUN + +This section suggests using precompiled binaries, this way you get everything bundled into one single package for an easy installation. + +If you want the newest, shiniest features, you can run Printrun from source using the instructions further down this README. + +## Windows + +A precompiled version is available at http://koti.kapsi.fi/~kliment/printrun/ + +## Mac OS X + +A precompiled version is available at http://koti.kapsi.fi/~kliment/printrun/ + +## Linux +### Ubuntu/Debian + +You can run Printrun directly from source. Fetch and install the dependencies using + +1. `sudo apt-get install python-serial python-wxgtk2.8 python-pyglet python-numpy cython python-libxml2 python-gobject python-dbus python-psutil python-cairosvg git` + +Clone the repository + +`git clone https://github.com/kliment/Printrun.git` + +and you can start using Printrun from the Printrun directory created by the git clone command. + +Users of Ubuntu Xenial Xerus or later and Debian Jessie or later won't find the package "python-wxgtk2.8" available, having been replaced by "python-wxgtk3.0". Running Printrun with "python-wxgtk3.0" instead, is possible but it is known to be affected by issue #615. + +A Printrun preliminary package is already available in Ubuntu Yakkety Yak and Debian Stretch/Sid repositories. Please be aware that this initial package is also known to be affected by issue #615. + +### Chrome OS + +You can use Printrun via crouton ( https://github.com/dnschneid/crouton ). Assuming you want Ubuntu Trusty, you used probably `sudo sh -e ~/Downloads/crouton -r trusty -t xfce` to install Ubuntu. Fetch and install dependencies with the line given above for Ubuntu/Debian, and obtain the source via git clone. + +By default you have no access to the serial port under Chrome OS crouton, so you cannot connect to your 3D printer. Add yourself to the serial group within the linux environment to fix this + +`sudo usermod -G serial -a ` + +where `` should be your username. Log out and in to make this group change active and allow communication with your printer. + +### Fedora + +You can install Printrun from official packages. Install the whole package using + +`sudo yum install printrun` + +Or get only apps you need by + +`sudo yum install pronsole` or `pronterface` or `plater` + +Adding `--enablerepo updates-testing` option to `yum` might give you newer packages (but also not very tested). + +You can also run Printrun directly from source, if the packages are too old for you. Fetch and install the dependencies using + +1. `sudo yum install pyserial wxPython python-pyglet python-cairosvg` + +Optional: `sudo yum install skeinforge simarrange` + +### Archlinux + +Packages are available in AUR. Just run + +`yaourt printrun` + +and enjoy the `pronterface`, `pronsole`, ... commands directly. + +## RUNNING FROM SOURCE + +Run Printrun for source if you want to test out the latest features. + +### Dependencies + +To use pronterface, you need: + + * python (ideally 2.6.x or 2.7.x), + * pyserial (or python-serial on ubuntu/debian) + * pyreadline (not needed on Linux) and + * argparse (installed by default with python >= 2.7) + * wxPython (some features such as Tabbed mode work better with wx 2.9) + * pyglet + * numpy (for 3D view) + * pycairo (to use Projector feature) + * cairosvg (to use Projector feature) + * dbus (to inhibit sleep on some Linux systems) + +Please see specific instructions for Windows and Mac OS X below. Under Linux, you should use your package manager directly (see the "GETTING PRINTRUN" section), or pip: + +```pip install -r requirements.txt``` + +### Cython-based G-Code parser + +Printrun default G-Code parser is quite memory hungry, but we also provide a much lighter one which just needs an extra build-time dependency (Cython), plus compiling the extension with: + + python setup.py build_ext --inplace + +The warning message + + WARNING:root:Memory-efficient GCoder implementation unavailable: No module named gcoder_line + +means that this optimized G-Code parser hasn't been compiled. To get rid of it and benefit from the better implementation, please install Cython and run the command above. + +### Windows + +Download the following, and install in this order: + + 1. http://python.org/ftp/python/2.7.2/python-2.7.2.msi + 2. http://pypi.python.org/packages/any/p/pyserial/pyserial-2.5.win32.exe + 3. http://downloads.sourceforge.net/wxpython/wxPython2.8-win32-unicode-2.8.12.0-py27.exe + 4. https://pypi.python.org/packages/any/p/pyreadline/pyreadline-1.7.1.win32.exe + 5. http://pyglet.googlecode.com/files/pyglet-1.1.4.zip + +For the last one, you will need to unpack it, open a command terminal, +go into the the directory you unpacked it in and run +`python setup.py install` + +### Mac OS X Lion + + 1. Ensure that the active Python is the system version. (`brew uninstall python` or other appropriate incantations) + 2. Download an install [wxPython2.8-osx-unicode] matching to your python version (most likely 2.7 on Lion, + check with: python --version) from: http://wxpython.org/download.php#stable + Known to work PythonWX: http://superb-sea2.dl.sourceforge.net/project/wxpython/wxPython/2.8.12.1/wxPython2.8-osx-unicode-2.8.12.1-universal-py2.7.dmg + 3. Download and unpack pyserial from http://pypi.python.org/packages/source/p/pyserial/pyserial-2.5.tar.gz + 4. In a terminal, change to the folder you unzipped to, then type in: `sudo python setup.py install` + 5. Repeat 4. with http://http://pyglet.googlecode.com/files/pyglet-1.1.4.zip + +The tools will probably run just fine in 64bit on Lion, you don't need to mess +with any of the 32bit settings. In case they don't, try + 5. export VERSIONER_PYTHON_PREFER_32_BIT=yes +in a terminal before running Pronterface + +### Mac OS X (pre Lion) + +A precompiled version is available at http://koti.kapsi.fi/~kliment/printrun/ + + 1. Download and install http://downloads.sourceforge.net/wxpython/wxPython2.8-osx-unicode-2.8.12.0-universal-py2.6.dmg + 2. Grab the source for pyserial from http://pypi.python.org/packages/source/p/pyserial/pyserial-2.5.tar.gz + 3. Unzip pyserial to a folder. Then, in a terminal, change to the folder you unzipped to, then type in: + + `defaults write com.apple.versioner.python Prefer-32-Bit -bool yes` + + `sudo python setup.py install` + +Alternatively, you can run python in 32 bit mode by setting the following environment variable before running the setup.py command: + +This alternative approach is confirmed to work on Mac OS X 10.6.8. + +`export VERSIONER_PYTHON_PREFER_32_BIT=yes` + +`sudo python setup.py install` + +Then repeat the same with http://http://pyglet.googlecode.com/files/pyglet-1.1.4.zip + +# USING PRINTRUN + +## USING PRONTERFACE + +When you're done setting up Printrun, you can start pronterface.py in the directory you unpacked it. +Select the port name you are using from the first drop-down, select your baud rate, and hit connect. +Load an STL (see the note on skeinforge below) or GCODE file, and you can upload it to SD or print it directly. +The "monitor printer" function, when enabled, checks the printer state (temperatures, SD print progress) every 3 seconds. +The command box recognizes all pronsole commands, but has no tabcompletion. + +If you want to load stl files, you need to install a slicing program such as Slic3r and add its path to the settings. +See the Slic3r readme for more details on integration. + + +## USING PRONSOLE + +To use pronsole, you need: + + * python (ideally 2.6.x or 2.7.x), + * pyserial (or python-serial on ubuntu/debian) and + * pyreadline (not needed on Linux) + +Start pronsole and you will be greeted with a command prompt. Type help to view the available commands. +All commands have internal help, which you can access by typing "help commandname", for example "help connect" + +If you want to load stl files, you need to put a version of skeinforge (doesn't matter which one) in a folder called "skeinforge". +The "skeinforge" folder must be in the same folder as pronsole.py + +## USING PRINTCORE + +To use printcore you need python (ideally 2.6.x or 2.7.x) and pyserial (or python-serial on ubuntu/debian) +See pronsole for an example of a full-featured host, the bottom of printcore.py for a simple command-line +sender, or the following code example: + +```python +#to send a file of gcode to the printer +from printrun.printcore import printcore +from printrun import gcoder +p=printcore('/dev/ttyUSB0',115200) # or p.printcore('COM3',115200) on Windows +gcode=[i.strip() for i in open('filename.gcode')] # or pass in your own array of gcode lines instead of reading from a file +gcode = gcoder.LightGCode(gcode) +p.startprint(gcode) # this will start a print + +#If you need to interact with the printer: +p.send_now("M105") # this will send M105 immediately, ahead of the rest of the print +p.pause() # use these to pause/resume the current print +p.resume() +p.disconnect() # this is how you disconnect from the printer once you are done. This will also stop running prints. +``` + +## PLATERS + +Printrun provides two platers: a STL plater (```plater.py```) and a G-Code plater (```gcodeplater.py```). + +## 3D VIEWER CONTROLS + +When the 3D viewer is enabled, the controls are the following: +- Mousewheel: zoom (Control reduces the zoom change steps) +- Shift+mousewheel: explore layers (in print gcode view ; Control key makes layer change by increments of 10 instead of 1) or rotate object (in platers) +- Left-click dragging: rotate view +- Right-click dragging: pan view +- Shift + left-click dragging: move object (in platers) +- Page up/down keys: zoom (Control reduces the zoom change steps) +- Up/down keys: explore layers +- R key: reset view +- F key: fit view to display entire print +- C key: toggle "display current layer only" mode (in print gcode view) + +## RPC SERVER + +```pronterface``` and ```pronsole``` start a RPC server, which runs by default +on localhost port 7978, which provides print progress information. +Here is a sample Python script querying the print status: + +```python +import xmlrpclib + +rpc = xmlrpclib.ServerProxy('http://localhost:7978') +print rpc.status() +``` + +## CONFIGURATION + +### Build dimensions + +Build dimensions can be specified using the build_dimensions option (which can +be graphically edited in Pronterface settings). This option is formed of 9 parameters: +3 for the build volume dimensions, 3 for the build volume coordinate system +offset minimum, 3 for the endstop positions. + +The default value is `200x200x100+0+0+0+0+0+0`, which corresponds to a +200x200mm (width x height) bed with 100mm travel in Z (there are the first +three numbers) and no offset. The absolute coordinates system origin (0,0,0) is +at the bottom left corner on the bed surface, and the top right corner on the +bed surface is (200,200,0). + +A common practice is to have the origin of the coordinate system (0,0,0) at the +center of the bed surface. This is achieved by using the next three parameters, +for instance with `200x200x100-100-100+0+0+0+0`. +In this case, the bottom left corner of the bed will be at (-100,-100,0) and +the top right one at (100,100,0). + +These two sets of settings should be sufficient for most people. However, for +some specific complicated setups and GCodes and some features, we might also +need the endstops positions for perfect display. These positions (which are +usually 0,0,0, so if you don't know you probably have a standard setup) are +specified in absolute coordinates, so if you have your bed starting at +(-100,-100,0) and your endstops are 10mm away from the bed left and right and +the Z endstop 5mm above the bed, you'll want to set the endstops positions to +(-110,-110,5) for this option. + +## USING MACROS AND CUSTOM BUTTONS + +### Macros in pronsole and pronterface + +To send simple G-code (or pronsole command) sequence is as simple as entering them one by one in macro definition. +If you want to use parameters for your macros, substitute them with {0} {1} {2} ... etc. + +All macros are saved automatically immediately after being entered. + +Example 1, simple one-line alias: + +```python +PC> macro where M114 +``` + +Instead of having to remember the code to query position, you can query the position: + +```python +PC> where +X:25.00Y:11.43Z:5.11E:0.00 +``` + +Example 2 - macros to switch between different slicer programs, using "set" command to change options: + +```python +PC> macro use_slicer +Enter macro using indented lines, end with empty line +..> set sliceoptscommand Slic3r/slic3r.exe --load slic3r.ini +..> set slicecommand Slic3r/slic3r.exe $s --load slic3r.ini --output $o +Macro 'use_slicer' defined +PC> macro use_sfact +..> set sliceoptscommand python skeinforge/skeinforge_application/skeinforge.py +..> set slicecommand python skeinforge/skeinforge_application/skeinforge_utilities/skeinforge_craft.py $s +Macro 'use_sfact' defined +``` + +Example 3, simple parametric macro: + +```python +PC> macro move_down_by +Enter macro using indented lines, end with empty line +..> G91 +..> G1 Z-{0} +..> G92 +..> +``` + +Invoke the macro to move the printhead down by 5 millimeters: + +```python +PC> move_down_by 5 +``` + +For more powerful macro programming, it is possible to use python code escaping using ! symbol in front of macro commands. +Note that this python code invocation also works in interactive prompt: + +```python +PC> !print "Hello, printer!" +Hello printer! + +PC> macro debug_on !self.p.loud = 1 +Macro 'debug_on' defined +PC> debug_on +PC> M114 +SENT: M114 +X:0.00Y:0.00Z:0.00E:0.00 Count X:0.00Y:0.00Z:0.00 +RECV: X:0.00Y:0.00Z:0.00E:0.00 Count X:0.00Y:0.00Z:0.00 +RECV: ok +``` + +You can use macro command itself to create simple self-modify or toggle functionality: + +Example: swapping two macros to implement toggle: + +```python +PC> macro toggle_debug_on +Enter macro using indented lines, end with empty line +..> !self.p.loud = 1 +..> !print "Diagnostic information ON" +..> macro toggle_debug toggle_debug_off +..> +Macro 'toggle_debug_on' defined +PC> macro toggle_debug_off +Enter macro using indented lines, end with empty line +..> !self.p.loud = 0 +..> !print "Diagnostic information OFF" +..> macro toggle_debug toggle_debug_on +..> +Macro 'toggle_debug_off' defined +PC> macro toggle_debug toggle_debug_on +Macro 'toggle_debug' defined +``` + +Now, each time we invoke "toggle_debug" macro, it toggles debug information on and off: + +```python +PC> toggle_debug +Diagnostic information ON + +PC> toggle_debug +Diagnostic information OFF +``` + +When python code (using ! symbol) is used in macros, it is even possible to use blocks/conditionals/loops. +It is okay to mix python code with pronsole commands, just keep the python indentation. +For example, following macro toggles the diagnostic information similarily to the previous example: + +```python +!if self.p.loud: + !self.p.loud = 0 + !print "Diagnostic information OFF" +!else: + !self.p.loud = 1 + !print "Diagnostic information ON" +``` + +Macro parameters are available in '!'-escaped python code as locally defined list variable: arg[0] arg[1] ... arg[N] + +All python code is executed in the context of the pronsole (or PronterWindow) object, +so it is possible to use all internal variables and methods, which provide great deal of functionality. +However the internal variables and methods are not very well documented and may be subject of change, as the program is developed. +Therefore it is best to use pronsole commands, which easily contain majority of the functionality that might be needed. + +Some useful python-mode-only variables: + +```python +!self.settings - contains all settings, e.g. + port (!self.settings.port), baudrate, xy_feedrate, e_feedrate, slicecommand, final_command, build_dimensions + You can set them also via pronsole command "set", but you can query the values only via python code. +!self.p - printcore object (see USING PRINTCORE section for using printcore object) +!self.cur_button - if macro was invoked via custom button, the number of the custom button, e.g. for usage in "button" command +!self.gwindow - wx graphical interface object for pronterface (highly risky to use because the GUI implementation details may change a lot between versions) +``` + +Some useful methods: + +```python +!self.onecmd - invokes raw command, e.g. + !self.onecmd("move x 10") + !self.onecmd("!print self.p.loud") + !self.onecmd("button "+self.cur_button+" fanOFF /C cyan M107") +!self.project - invoke Projector +``` + +## USING HOST COMMANDS + +Pronsole and the console interface in Pronterface accept a number of commands +which you can either use directly or inside your G-Code. To run a host command +from inside a G-Code, simply prefix it with `;@`. + +List of available commands: + +- `pause`: pauses the print until the user resumes it +- `run_script scriptname [arg1 ...]`: runs a custom script or program on the + host computer. This can for instance be used to produce a sound to warn the + user (e.g. `run_script beep -r 2` on machines were the `beep` util is + available), or to send an email or text message at the end of a print. The $s + token can be used in the arguments to get the current gcode file name +- `run_gcode_script scripname [arg1 ...]`: same as `run_script`, except that + all lines displayed by the script will be interpreted in turn (so that G-Code + lines will be immediately sent to the printer) +- `shell pythoncommand`: run a python command (can also be achieved by doing + `!pythoncommand`) +- `set option value`: sets the value of an option, e.g. `set mainviz 3D` +- `connect` +- `block_until_online`: wait for the printer to be online. For instance you can + do `python pronsole.py -e "connect" -e "block_until_online" -e "upload + object.gcode"` to start pronsole, connect for the printer, wait for it to be + online to start uploading the `object.gcode` file. +- `disconnect` +- `load gcodefile` +- `upload gcodefile target.g`: upload `gcodefile` to `target.g` on the SD card +- `slice stlfile`: slice `stlfile` and load the produced G-Code +- `print`: print the currently loaded file +- `sdprint target.g`: start a SD print +- `ls`: list files on SD card +- `eta`: display remaining print time +- `gettemp`: get current printer temperatures +- `settemp`: set hotend target temperature +- `bedtemp`: set bed target temperature +- `monitor`: monitor printer progress during a print +- `tool K`: switch to tool K +- `move xK`: move along `x` axis (works with other axes too) +- `extrude length [speed]` +- `reverse length [speed]` +- `home [axis]` +- `off`: turns off fans, motors, extruder, heatbed, power supply +- `exit` + +# LICENSE + +``` +Printrun is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Printrun is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Printrun. If not, see . +``` + +All scripts should contain this license note, if not, feel free to ask us. Please note that files where it is difficult to state this license note (such as images) are distributed under the same terms. diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/TODO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/TODO Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,11 @@ +* [ ] Rework gcview zooming/rotation/move behavior +* [ ] Improve stlview +* [x] Add tool to select gcode area to ignore +* [ ] Add calibration helper +* [ ] Improve tabbed mode for wx 2.8 ? (the main toolbar is not wrapping as in 2.9) +- [ ] Review all settings long help +- [ ] Make heavy 3D viz work +- [ ] Add speed slider +- [ ] Fix code injection +- [ ] Improve gcode plater +- [ ] Check what happens for layer switch for 2D viewer diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/auth.config --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/auth.config Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,3 @@ +[user] +user = admin +pass = password \ No newline at end of file diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/calibrateextruder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/calibrateextruder.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,168 @@ +#!/usr/bin/env python +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +# Interactive RepRap e axis calibration program +# (C) Nathan Zadoks 2011 + +s = 300 # Extrusion speed (mm/min) +n = 100 # Default length to extrude +m = 0 # User-entered measured extrusion length +k = 300 # Default amount of steps per mm +port = '/dev/ttyUSB0' # Default serial port to connect to printer +temp = 210 # Default extrusion temperature + +tempmax = 250 # Maximum extrusion temperature + +t = int(n * 60) / s # Time to wait for extrusion + +try: + from printdummy import printcore +except ImportError: + from printcore import printcore + +import time +import getopt +import sys +import os + +def float_input(prompt=''): + f = None + while f is None: + s = raw_input(prompt) + try: + f = float(s) + except ValueError: + sys.stderr.write("Not a valid floating-point number.\n") + sys.stderr.flush() + return f +def wait(t, m=''): + sys.stdout.write(m + '[' + (' ' * t) + ']\r' + m + '[') + sys.stdout.flush() + for i in range(t): + for s in ['|\b', '/\b', '-\b', '\\\b', '|']: + sys.stdout.write(s) + sys.stdout.flush() + time.sleep(1.0 / 5) + print +def w(s): + sys.stdout.write(s) + sys.stdout.flush() + + +def heatup(p, temp, s = 0): + curtemp = gettemp(p) + p.send_now('M109 S%03d' % temp) + p.temp = 0 + if not s: w("Heating extruder up..") + f = False + while curtemp <= (temp - 1): + p.send_now('M105') + time.sleep(0.5) + if not f: + time.sleep(1.5) + f = True + curtemp = gettemp(p) + if curtemp: w(u"\rHeating extruder up.. %3d \xb0C" % curtemp) + if s: print + else: print "\nReady." + +def gettemp(p): + try: p.logl + except: setattr(p, 'logl', 0) + try: p.temp + except: setattr(p, 'temp', 0) + for n in range(p.logl, len(p.log)): + line = p.log[n] + if 'T:' in line: + try: + setattr(p, 'temp', int(line.split('T:')[1].split()[0])) + except: print line + p.logl = len(p.log) + return p.temp +if not os.path.exists(port): + port = 0 + +# Parse options +help = u""" +%s [ -l DISTANCE ] [ -s STEPS ] [ -t TEMP ] [ -p PORT ] + -l --length Length of filament to extrude for each calibration step (default: %d mm) + -s --steps Initial amount of steps to use (default: %d steps) + -t --temp Extrusion temperature in degrees Celsius (default: %d \xb0C, max %d \xb0C) + -p --port Serial port the printer is connected to (default: %s) + -h --help This cruft. +"""[1:-1].encode('utf-8') % (sys.argv[0], n, k, temp, tempmax, port if port else 'auto') +try: + opts, args = getopt.getopt(sys.argv[1:], "hl:s:t:p:", ["help", "length=", "steps=", "temp=", "port="]) +except getopt.GetoptError, err: + print str(err) + print help + sys.exit(2) +for o, a in opts: + if o in ('-h', '--help'): + print help + sys.exit() + elif o in ('-l', '--length'): + n = float(a) + elif o in ('-s', '--steps'): + k = int(a) + elif o in ('-t', '--temp'): + temp = int(a) + if temp >= tempmax: + print (u'%d \xb0C? Are you insane?'.encode('utf-8') % temp) + (" That's over nine thousand!" if temp > 9000 else '') + sys.exit(255) + elif o in ('-p', '--port'): + port = a + +# Show initial parameters +print "Initial parameters" +print "Steps per mm: %3d steps" % k +print "Length extruded: %3d mm" % n +print +print "Serial port: %s" % (port if port else 'auto') + +p = None +try: + # Connect to printer + w("Connecting to printer..") + try: + p = printcore(port, 115200) + except: + print 'Error.' + raise + while not p.online: + time.sleep(1) + w('.') + print " connected." + + heatup(p, temp) + + # Calibration loop + while n != m: + heatup(p, temp, True) + p.send_now("G92 E0") # Reset e axis + p.send_now("G1 E%d F%d" % (n, s)) # Extrude length of filament + wait(t, 'Extruding.. ') + m = float_input("How many millimeters of filament were extruded? ") + if m == 0: continue + if n != m: + k = (n / m) * k + p.send_now("M92 E%d" % int(round(k))) # Set new step count + print "Steps per mm: %3d steps" % k # Tell user + print 'Calibration completed.' # Yay! +except KeyboardInterrupt: + pass +finally: + if p: p.disconnect() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/custombtn.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/custombtn.txt Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,17 @@ +btns=[ +###Defining custom buttons for pronterface is easy. Here's how. +###Below these instructions, add a line with the following format for each button +# ["button name","command",color(RGB)], <--That comma is important, do not forget it +###As an example: (Remove the # to try it out): +#["Read temp","M105",(200,100,100)], +#["Disable Axes","M84",(400,100,100)], +###You can use gcodes or any pronsole/pronterface commands +###The first four buttons will end up at the top of the window, visible in mini mode +###The rest of the buttons will be at the bottom under the gcode preview +###ADD BUTTON DEFINITIONS BELOW THIS LINE + + + + +###ADD BUTTON DEFINITIONS ABOVE THIS LINE +] diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/dot.pronsolerc.example --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/dot.pronsolerc.example Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,16 @@ +# Sample .pronsolerc file - copy this into your home directory and rename it to .pronsolerc +!print "Loaded " + self.rc_filename + +macro fan + !global _fan + !if '_fan' in globals() and _fan: + !_fan = 0 + M107 + !if hasattr(self,"cur_button") and self.cur_button is not None: + !self.onecmd('button %d "fan (off)" /c green fan' % self.cur_button) + !else: + !_fan = 1 + M106 + !if hasattr(self,"cur_button") and self.cur_button is not None: + !self.onecmd('button %d "fan (on)" /c yellow fan' % self.cur_button) +button 0 "fan (off)" /c "green" fan diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/gcodeplater.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/gcodeplater.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import sys +import wx + +from printrun.gcodeplater import GcodePlater + +if __name__ == '__main__': + app = wx.App(False) + main = GcodePlater(filenames = sys.argv[1:]) + main.Show() + app.MainLoop() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/arrow_down.png Binary file printrun-src/images/arrow_down.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/arrow_keys.png Binary file printrun-src/images/arrow_keys.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/arrow_up.png Binary file printrun-src/images/arrow_up.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/control_mini.png Binary file printrun-src/images/control_mini.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/control_mini.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/images/control_mini.svg Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,447 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/control_xy.png Binary file printrun-src/images/control_xy.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/control_xy.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/images/control_xy.svg Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/control_z.png Binary file printrun-src/images/control_z.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/control_z.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/images/control_z.svg Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/control_z_mini.png Binary file printrun-src/images/control_z_mini.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/control_z_mini.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/images/control_z_mini.svg Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/edit.png Binary file printrun-src/images/edit.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/fit.png Binary file printrun-src/images/fit.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/inject.png Binary file printrun-src/images/inject.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/reset.png Binary file printrun-src/images/reset.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/zoom_in.png Binary file printrun-src/images/zoom_in.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/images/zoom_out.png Binary file printrun-src/images/zoom_out.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/ar/LC_MESSAGES/plater.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/ar/LC_MESSAGES/plater.po Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,94 @@ + +msgid "" +msgstr "" +"Project-Id-Version:Plater\n" +"POT-Creation-Date: 2012-01-09 15:07+CET\n" +"PO-Revision-Date: 2015-06-25 07:49+0300\n" +"Last-Translator: سـند <0sanad0@gmail.com>\n" +"Language-Team: < http://linuxac.org ســ <مجتمع لينكس العربي\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" +"X-Generator: Virtaal 0.7.1\n" +"Generated-By: pygettext.py 1.5\n" + +#: plater.py:246 +msgid "Plate building tool" +msgstr "أداة بناء اللوحة" + +#: plater.py:252 +msgid "Clear" +msgstr "" + +#: plater.py:253 +msgid "Load" +msgstr "تحميل" + +#: plater.py:255 plater.py:258 +msgid "Export" +msgstr "تصدير" + +#: plater.py:260 +msgid "Done" +msgstr "حرر؛ عمل" + +#: plater.py:262 +msgid "Cancel" +msgstr "الغِ" + +#: plater.py:264 +msgid "Snap to Z = 0" +msgstr "" + +#: plater.py:265 +msgid "Put at 100, 100" +msgstr "" + +#: plater.py:266 +msgid "Delete" +msgstr "حذف" + +#: plater.py:267 +msgid "Auto" +msgstr "تلقائي" + +#: plater.py:291 +msgid "Autoplating" +msgstr " + +#: plater.py:319 +msgid "Bed full, sorry sir :(" +msgstr "" + +#: plater.py:329 +msgid "Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "" +"هل أنت متأكد من أنك تريد مسح الشبكة؟ سيتم فقدان كافة التغييرات التي لم يتم " +"حفظها." + +#: plater.py:329 +msgid "Clear the grid?" +msgstr "" + +#: plater.py:371 +msgid "Pick file to save to" +msgstr "" + +#: plater.py:372 +msgid "STL files (;*.stl;*.STL;)" +msgstr "ملفات STL (;*.stl;*.STL;)" + +#: plater.py:393 +msgid "wrote %s" +msgstr "كتب %s" + +#: plater.py:396 +msgid "Pick file to load" +msgstr "" + +#: plater.py:397 +msgid "STL files (;*.stl;*.STL;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" +msgstr "ملغات STL (;*.stl;*.STL;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/de/LC_MESSAGES/plater.mo Binary file printrun-src/locale/de/LC_MESSAGES/plater.mo has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/de/LC_MESSAGES/plater.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/de/LC_MESSAGES/plater.po Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,89 @@ +msgid "" +msgstr "" +"Project-Id-Version: Plater\n" +"POT-Creation-Date: 2012-01-09 15:07+CET\n" +"PO-Revision-Date: \n" +"Last-Translator: Christian Metzen \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: German\n" +"X-Poedit-Country: GERMANY\n" + +#: plater.py:223 +msgid "Plate building tool" +msgstr "Plate building tool" + +#: plater.py:229 +msgid "Clear" +msgstr "Löschen" + +#: plater.py:230 +msgid "Load" +msgstr "Laden" + +#: plater.py:232 +msgid "Export" +msgstr "Exportieren" + +#: plater.py:235 +msgid "Done" +msgstr "Fertig" + +#: plater.py:237 +msgid "Cancel" +msgstr "Abbrechen" + +#: plater.py:239 +msgid "Snap to Z = 0" +msgstr "Einrasten auf Z = 0" + +#: plater.py:240 +msgid "Put at 100, 100" +msgstr "Auf 100, 100 setzen" + +#: plater.py:241 +msgid "Delete" +msgstr "Löschen" + +#: plater.py:242 +msgid "Auto" +msgstr "Auto" + +#: plater.py:266 +msgid "Autoplating" +msgstr "Autoplating" + +#: plater.py:294 +msgid "Bed full, sorry sir :(" +msgstr "Das Druckbett ist voll! Sorry." + +#: plater.py:304 +msgid "Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "Bist du sicher dass du das Raster leeren willst? Alle ungesicherten Änderungen gehen verloren." + +#: plater.py:304 +msgid "Clear the grid?" +msgstr "Raster leeren?" + +#: plater.py:346 +msgid "Pick file to save to" +msgstr "Wähle die zu sichernde Datei" + +#: plater.py:347 +msgid "STL files (;*.stl;)" +msgstr "STL Dateien (;*.stl;)" + +#: plater.py:367 +msgid "wrote " +msgstr "geschrieben" + +#: plater.py:370 +msgid "Pick file to load" +msgstr "Wähle die zu ladende Datei" + +#: plater.py:371 +msgid "STL files (;*.stl;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" +msgstr "STL Dateien (;*.stl;)|*.stl|OpenSCAD Dateien (;*.scad;)|*.scad" + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/de/LC_MESSAGES/pronterface.mo Binary file printrun-src/locale/de/LC_MESSAGES/pronterface.mo has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/de/LC_MESSAGES/pronterface.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/de/LC_MESSAGES/pronterface.po Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,2211 @@ +# Pronterface +# Copyright (C) 2011 Jonathan Marsden +# Jonathan Marsden , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: Pronterface jm1\n" +"POT-Creation-Date: 2014-03-30 14:35+CEST\n" +"PO-Revision-Date: 2012-01-23 10:01+0100\n" +"Last-Translator: Christian Metzen \n" +"Language-Team: DE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + +#: printrun/excluder.py:26 +msgid "" +"Part excluder: draw rectangles where print instructions should be ignored" +msgstr "" + +#: printrun/excluder.py:27 printrun/excluder.py:29 +msgid "Reset selection" +msgstr "" + +#: printrun/gcodeplater.py:43 printrun/gcodeplater.py:44 +msgid "GCODE files (*.gcode;*.GCODE;*.g)" +msgstr "" + +#: printrun/gcodeplater.py:112 printrun/gcodeplater.py:152 +msgid "Warning: no rotation support for now, object won't be correctly rotated" +msgstr "" + +#: printrun/gcodeplater.py:141 printrun/gcodeplater.py:178 +msgid "Exported merged G-Codes to %s" +msgstr "" + +#: printrun/gcview.py:352 +msgid "Fit to plate" +msgstr "" + +#: printrun/gcview.py:353 +msgid "Fit to plate [F]" +msgstr "" + +#: printrun/gui/__init__.py:19 printrun/pronterface.py:45 +msgid "WX is not installed. This program requires WX to run." +msgstr "WX ist nicht installiert. Dieses Programm erfordert WX zum Starten." + +#: printrun/gui/__init__.py:159 +#, fuzzy +msgid "Commands" +msgstr "Kommando" + +#: printrun/gui/__init__.py:160 +msgid "Status" +msgstr "" + +#: printrun/gui/controls.py:126 +#, fuzzy +msgid "Heat:" +msgstr "Heizelement:" + +#: printrun/gui/controls.py:129 +msgid "Switch Hotend Off" +msgstr "" + +#: printrun/gui/controls.py:129 printrun/gui/controls.py:149 +#: printrun/gui/toolbar.py:85 +msgid "Off" +msgstr "Aus" + +#: printrun/gui/controls.py:137 +#, fuzzy +msgid "Select Temperature for Hotend" +msgstr "Letzte Hotend Temperatur" + +#: printrun/gui/controls.py:141 +msgid "Switch Hotend On" +msgstr "" + +#: printrun/gui/controls.py:141 printrun/gui/controls.py:161 +#: printrun/gui/controls.py:198 +msgid "Set" +msgstr "Ein" + +#: printrun/gui/controls.py:146 printrun/gui/controls.py:215 +msgid "Bed:" +msgstr "Heizbett:" + +#: printrun/gui/controls.py:149 +msgid "Switch Heated Bed Off" +msgstr "" + +#: printrun/gui/controls.py:157 +#, fuzzy +msgid "Select Temperature for Heated Bed" +msgstr "Letzte gesetzte Temperatur für das Heizbett" + +#: printrun/gui/controls.py:161 +msgid "Switch Heated Bed On" +msgstr "" + +#: printrun/gui/controls.py:187 +msgid "Print speed:" +msgstr "" + +#: printrun/gui/controls.py:192 printrun/gui/controls.py:207 +msgid "%d%%" +msgstr "" + +#: printrun/gui/controls.py:198 +msgid "Set print speed factor" +msgstr "" + +#: printrun/gui/controls.py:213 +msgid "Heater:" +msgstr "Heizelement:" + +#: printrun/gui/controls.py:265 +msgid "Length:" +msgstr "" + +#: printrun/gui/controls.py:267 +msgid "mm" +msgstr "mm" + +#: printrun/gui/controls.py:267 +msgid "mm @" +msgstr "" + +#: printrun/gui/controls.py:269 +msgid "Amount to Extrude or Retract (mm)" +msgstr "" + +#: printrun/gui/controls.py:272 +msgid "Extrude / Retract speed (mm/min)" +msgstr "" + +#: printrun/gui/controls.py:278 +msgid "Speed:" +msgstr "" + +#: printrun/gui/controls.py:279 +msgid "" +"mm/\n" +"min" +msgstr "" + +#: printrun/gui/controls.py:290 +msgid "Tool:" +msgstr "" + +#: printrun/gui/controls.py:293 +msgid "Click to switch current extruder" +msgstr "" + +#: printrun/gui/controls.py:310 +msgid "Select current extruder" +msgstr "" + +#: printrun/gui/controls.py:371 +msgid "Set Maximum Speed for X & Y axes (mm/min)" +msgstr "" + +#: printrun/gui/controls.py:372 +msgid "XY:" +msgstr "XY:" + +#: printrun/gui/controls.py:374 +#, fuzzy +msgid "mm/min Z:" +msgstr "mm/min" + +#: printrun/gui/controls.py:376 +msgid "Set Maximum Speed for Z axis (mm/min)" +msgstr "" + +#: printrun/gui/graph.py:28 +msgid "Temperature graph" +msgstr "" + +#: printrun/gui/log.py:32 +msgid "" +"Send commands to printer\n" +"(Type 'help' for simple\n" +"help function)" +msgstr "" + +#: printrun/gui/log.py:39 +msgid "Send" +msgstr "Senden" + +#: printrun/gui/log.py:39 +#, fuzzy +msgid "Send Command to Printer" +msgstr "Drucker überwachen" + +#: printrun/gui/toolbar.py:23 +msgid "Lock" +msgstr "" + +#: printrun/gui/toolbar.py:25 +msgid "Lock graphical interface" +msgstr "" + +#: printrun/gui/toolbar.py:32 +msgid "" +"Communication Settings\n" +"Click to rescan ports" +msgstr "" + +#: printrun/gui/toolbar.py:32 +msgid "Port" +msgstr "Port:" + +#: printrun/gui/toolbar.py:37 +msgid "Select Port Printer is connected to" +msgstr "" + +#: printrun/gui/toolbar.py:46 +msgid "Select Baud rate for printer communication" +msgstr "" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1052 +msgid "Connect" +msgstr "Verbinden" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1053 +msgid "Connect to the printer" +msgstr "Drucker Verbinden" + +#: printrun/gui/toolbar.py:61 +msgid "Reset" +msgstr "Zurücksetzen" + +#: printrun/gui/toolbar.py:61 +#, fuzzy +msgid "Reset the printer" +msgstr "Drucker Verbinden" + +#: printrun/gui/toolbar.py:69 +#, fuzzy +msgid "Load a 3D model file" +msgstr "Datei laden" + +#: printrun/gui/toolbar.py:69 +msgid "Load file" +msgstr "Datei laden" + +#: printrun/gui/toolbar.py:70 +msgid "SD" +msgstr "SD" + +#: printrun/gui/toolbar.py:70 +#, fuzzy +msgid "SD Card Printing" +msgstr "SD Drucken" + +#: printrun/gui/toolbar.py:74 +msgid "Start Printing Loaded File" +msgstr "" + +#: printrun/gui/toolbar.py:74 printrun/pronterface.py:1003 +#: printrun/pronterface.py:1062 printrun/pronterface.py:1078 +#: printrun/pronterface.py:1363 printrun/pronterface.py:1445 +msgid "Print" +msgstr "Drucken" + +#: printrun/gui/toolbar.py:80 +msgid "Pause Current Print" +msgstr "" + +#: printrun/gui/toolbar.py:80 printrun/pronterface.py:1002 +#: printrun/pronterface.py:1061 printrun/pronterface.py:1081 +#: printrun/pronterface.py:1091 printrun/pronterface.py:1172 +#: printrun/pronterface.py:1364 +msgid "Pause" +msgstr "Pause" + +#: printrun/gui/toolbar.py:85 +msgid "Turn printer off" +msgstr "" + +#: printrun/gui/viz.py:65 +msgid "" +"Click to examine / edit\n" +" layers of loaded file" +msgstr "" + +#: printrun/gui/widgets.py:36 +msgid "Find" +msgstr "" + +#: printrun/gui/widgets.py:38 +msgid "Save" +msgstr "Speichern" + +#: printrun/gui/widgets.py:43 printrun/objectplater.py:76 +#: printrun/pronterface.py:1257 printrun/pronterface.py:2017 +msgid "Cancel" +msgstr "Abbrechen" + +#: printrun/gui/widgets.py:70 +#, fuzzy +msgid "Not Found!" +msgstr "Datei nicht gefunden!" + +#: printrun/gui/widgets.py:124 +#, fuzzy +msgid "Printer settings" +msgstr "Einstellungen bearbeiten" + +#: printrun/gui/widgets.py:125 +#, fuzzy +msgid "User interface" +msgstr "Printer Interface" + +#: printrun/gui/widgets.py:126 +#, fuzzy +msgid "Colors" +msgstr "Farbe" + +#: printrun/gui/widgets.py:127 +#, fuzzy +msgid "External commands" +msgstr "Kommando ausführen:" + +#: printrun/gui/widgets.py:132 +msgid "Edit settings" +msgstr "Einstellungen bearbeiten" + +#: printrun/gui/widgets.py:135 +#, fuzzy +msgid "Settings" +msgstr "&Einstellungen" + +#: printrun/gui/widgets.py:187 +msgid "Custom button" +msgstr "Individueller Button" + +#: printrun/gui/widgets.py:193 +msgid "Button title" +msgstr "Button Titel" + +#: printrun/gui/widgets.py:196 +msgid "Command" +msgstr "Kommando" + +#: printrun/gui/widgets.py:205 +msgid "Color" +msgstr "Farbe" + +#: printrun/gviz.py:29 +msgid "Layer number and Z position show here when you scroll" +msgstr "" + +#: printrun/gviz.py:39 +msgid "Zoom In [+]" +msgstr "" + +#: printrun/gviz.py:40 +msgid "Zoom Out [-]" +msgstr "" + +#: printrun/gviz.py:42 +msgid "Move Up a Layer [U]" +msgstr "" + +#: printrun/gviz.py:43 +msgid "Move Down a Layer [D]" +msgstr "" + +#: printrun/gviz.py:44 +#, fuzzy +msgid "Reset view" +msgstr "Zurücksetzen" + +#: printrun/gviz.py:68 +msgid "Gcode view, shift to move view, mousewheel to set layer" +msgstr "" + +#: printrun/gviz.py:105 printrun/gviz.py:241 +msgid "Layer %d - Going Up - Z = %.03f mm" +msgstr "" + +#: printrun/gviz.py:250 +msgid "Layer %d - Going Down - Z = %.03f mm" +msgstr "" + +#: printrun/objectplater.py:35 +msgid "Plate building tool" +msgstr "" + +#: printrun/objectplater.py:45 +msgid "Clear" +msgstr "" + +#: printrun/objectplater.py:49 +#, fuzzy +msgid "Load" +msgstr "Geladen " + +#: printrun/objectplater.py:53 +msgid "Snap to Z = 0" +msgstr "" + +#: printrun/objectplater.py:57 +msgid "Put at center" +msgstr "" + +#: printrun/objectplater.py:61 +msgid "Delete" +msgstr "" + +#: printrun/objectplater.py:65 +msgid "Auto arrange" +msgstr "" + +#: printrun/objectplater.py:69 +msgid "Export" +msgstr "" + +#: printrun/objectplater.py:73 +msgid "Done" +msgstr "" + +#: printrun/objectplater.py:151 +msgid "Autoplating" +msgstr "" + +#: printrun/objectplater.py:199 +msgid "Bed full, sorry sir :(" +msgstr "" + +#: printrun/objectplater.py:209 +msgid "" +"Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "" + +#: printrun/objectplater.py:210 +#, fuzzy +msgid "Clear the grid?" +msgstr " Schliesst das Fenster" + +#: printrun/objectplater.py:256 +#, fuzzy +msgid "Pick file to load" +msgstr "Wähle SD Datei" + +#: printrun/objectplater.py:267 +#, fuzzy +msgid "Pick file to save to" +msgstr "Wähle SD Dateiname" + +#: printrun/plater.py:212 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL|OpenSCAD files (*.scad)|*.scad" +msgstr "" + +#: printrun/plater.py:213 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL" +msgstr "" + +#: printrun/plater.py:244 +#, fuzzy +msgid "Loading STL file failed" +msgstr "Datei öffnen fehlgeschlagen." + +#: printrun/plater.py:244 printrun/plater.py:251 +msgid "Error" +msgstr "" + +#: printrun/plater.py:251 +#, fuzzy +msgid "Loading OpenSCAD file failed" +msgstr "Datei öffnen fehlgeschlagen." + +#: printrun/plater.py:284 +msgid "Couldn't load non-existing file %s" +msgstr "" + +#: printrun/plater.py:360 +msgid "Wrote plate to %s" +msgstr "" + +#: printrun/plater.py:367 +msgid "" +"Failed to use simarrange for plating, falling back to the standard method" +msgstr "" + +#: printrun/plater.py:372 +msgid "Autoplating using simarrange" +msgstr "" + +#: printrun/plater.py:387 +msgid "Plate full, please remove some objects" +msgstr "" + +#: printrun/plater.py:404 +msgid "simarrange failed" +msgstr "" + +#: printrun/printcore.py:177 +msgid "Could not connect to %s:%s:" +msgstr "" + +#: printrun/printcore.py:178 +msgid "Socket error %s:" +msgstr "" + +#: printrun/printcore.py:191 printrun/printcore.py:196 +msgid "Could not connect to %s at baudrate %s:" +msgstr "" + +#: printrun/printcore.py:192 +msgid "Serial error: %s" +msgstr "" + +#: printrun/printcore.py:197 +msgid "IO error: %s" +msgstr "" + +#: printrun/printcore.py:231 +msgid "Can't read from printer (disconnected?) (SelectError {0}): {1}" +msgstr "" + +#: printrun/printcore.py:234 +msgid "SelectError ({0}): {1}" +msgstr "" + +#: printrun/printcore.py:237 +msgid "Can't read from printer (disconnected?) (SerialException): {0}" +msgstr "" + +#: printrun/printcore.py:240 +msgid "Can't read from printer (disconnected?) (Socket error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:245 +msgid "Can't read from printer (disconnected?) (OS Error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:259 +msgid "Aborting connection attempt after 4 failed writes." +msgstr "" + +#: printrun/printcore.py:462 printrun/printcore.py:470 +#: printrun/pronsole.py:1192 printrun/pronsole.py:1250 +#: printrun/pronterface.py:209 printrun/pronterface.py:962 +#: printrun/pronterface.py:1111 printrun/pronterface.py:1178 +msgid "Not connected to printer." +msgstr "Keine Verbindung zum Drucker." + +#: printrun/printcore.py:479 +msgid "Print start callback failed with:" +msgstr "" + +#: printrun/printcore.py:490 +msgid "Print end callback failed with:" +msgstr "" + +#: printrun/printcore.py:493 +msgid "Print thread died due to the following error:" +msgstr "" + +#: printrun/printcore.py:584 +msgid "Could not analyze command %s:" +msgstr "" + +#: printrun/printcore.py:601 +msgid "Can't write to printer (disconnected ?):" +msgstr "" + +#: printrun/printcore.py:604 +msgid "Can't write to printer (disconnected?) (Socket error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:607 +msgid "Can't write to printer (disconnected?) (SerialException): {0}" +msgstr "" + +#: printrun/printcore.py:610 +msgid "Socket connection broken, disconnected. ({0}): {1}" +msgstr "" + +#: printrun/pronsole.py:72 +#, fuzzy +msgid "Default: " +msgstr "Standardwerte" + +#: printrun/pronsole.py:73 +msgid "(Control-doubleclick to reset to default value)" +msgstr "" + +#: printrun/pronsole.py:108 +#, fuzzy +msgid "" +"Are you sure you want to reset the setting to the default value: {0!r} ?" +msgstr "Möchten Sie den Drucker wirklich zurücksetzen?" + +#: printrun/pronsole.py:108 +msgid "Confirm set default" +msgstr "" + +#: printrun/pronsole.py:258 +msgid "Width" +msgstr "" + +#: printrun/pronsole.py:261 +msgid "Depth" +msgstr "" + +#: printrun/pronsole.py:264 +msgid "Height" +msgstr "" + +#: printrun/pronsole.py:267 +msgid "X offset" +msgstr "" + +#: printrun/pronsole.py:270 +msgid "Y offset" +msgstr "" + +#: printrun/pronsole.py:273 +msgid "Z offset" +msgstr "" + +#: printrun/pronsole.py:276 +msgid "X home pos." +msgstr "" + +#: printrun/pronsole.py:279 +msgid "Y home pos." +msgstr "" + +#: printrun/pronsole.py:282 +msgid "Z home pos." +msgstr "" + +#: printrun/pronsole.py:301 +msgid "Port used to communicate with printer" +msgstr "Port für Druckerkommunikation" + +#: printrun/pronsole.py:301 +msgid "Serial port" +msgstr "" + +#: printrun/pronsole.py:302 +msgid "Baud rate" +msgstr "" + +#: printrun/pronsole.py:302 +#, fuzzy +msgid "Communications Speed" +msgstr "Kommunikationsgeschwindigkeit (Vorgabe: 115200)" + +#: printrun/pronsole.py:303 +msgid "TCP streaming mode" +msgstr "" + +#: printrun/pronsole.py:303 +msgid "" +"When using a TCP connection to the printer, the streaming mode will not wait " +"for acks from the printer to send new commands. This will break things such " +"as ETA prediction, but can result in smoother prints." +msgstr "" + +#: printrun/pronsole.py:304 +msgid "Bed temperature for ABS" +msgstr "" + +#: printrun/pronsole.py:304 +#, fuzzy +msgid "Heated Build Platform temp for ABS (deg C)" +msgstr "Heizbett Temp. für ABS (Vorgabe: 110 Grad Celsius)" + +#: printrun/pronsole.py:305 +msgid "Bed temperature for PLA" +msgstr "" + +#: printrun/pronsole.py:305 +#, fuzzy +msgid "Heated Build Platform temp for PLA (deg C)" +msgstr "Heizbett Temp. für PLA (Vorgabe: 60 Grad Celsius)" + +#: printrun/pronsole.py:306 +#, fuzzy +msgid "Extruder temp for ABS (deg C)" +msgstr "Extruder Temperatur für ABS (Vorgabe: 230 Grad Celsius)" + +#: printrun/pronsole.py:306 +#, fuzzy +msgid "Extruder temperature for ABS" +msgstr "Extruder Temperatur für ABS (Vorgabe: 230 Grad Celsius)" + +#: printrun/pronsole.py:307 +#, fuzzy +msgid "Extruder temp for PLA (deg C)" +msgstr "Extruder Temperatur für PLA (Vorgabe: 185 Grad Celsius)" + +#: printrun/pronsole.py:307 +#, fuzzy +msgid "Extruder temperature for PLA" +msgstr "Extruder Temperatur für PLA (Vorgabe: 185 Grad Celsius)" + +#: printrun/pronsole.py:308 +#, fuzzy +msgid "Feedrate for Control Panel Moves in X and Y (mm/min)" +msgstr "Vorschub Control Panel Bewegungen X und Y (Vorgabe: 3000mm/min)" + +#: printrun/pronsole.py:308 +msgid "X && Y manual feedrate" +msgstr "" + +#: printrun/pronsole.py:309 +#, fuzzy +msgid "Feedrate for Control Panel Moves in Z (mm/min)" +msgstr "Vorschub Control Panel Bewegungen Z (Vorgabe: 200mm/min)" + +#: printrun/pronsole.py:309 +msgid "Z manual feedrate" +msgstr "" + +#: printrun/pronsole.py:310 +msgid "E manual feedrate" +msgstr "" + +#: printrun/pronsole.py:310 +#, fuzzy +msgid "Feedrate for Control Panel Moves in Extrusions (mm/min)" +msgstr "Vorschub Control Panel Bewegungen Extrudierung (Vorgabe: 300mm/min)" + +#: printrun/pronsole.py:311 +#, fuzzy +msgid "Slice command" +msgstr "Kommando ausführen:" + +#: printrun/pronsole.py:312 +#, fuzzy +msgid "Slice settings command" +msgstr "Slicing Einstellungen" + +#: printrun/pronsole.py:312 +msgid "Slicer options command" +msgstr "" + +#: printrun/pronsole.py:313 +msgid "Executable to run when the print is finished" +msgstr "" + +#: printrun/pronsole.py:313 +#, fuzzy +msgid "Final command" +msgstr "Kommando" + +#: printrun/pronsole.py:314 +#, fuzzy +msgid "Error command" +msgstr "Kommando ausführen:" + +#: printrun/pronsole.py:314 +msgid "Executable to run when an error occurs" +msgstr "" + +#: printrun/pronsole.py:381 +msgid "Failed to run callback after setting \"%s\":" +msgstr "" + +#: printrun/pronsole.py:469 +msgid "Build dimensions" +msgstr "" + +#: printrun/pronsole.py:469 +#, fuzzy +msgid "" +"Dimensions of Build Platform\n" +" & optional offset of origin\n" +" & optional switch position\n" +"\n" +"Examples:\n" +" XXXxYYY\n" +" XXX,YYY,ZZZ\n" +" XXXxYYYxZZZ+OffX+OffY+OffZ\n" +"XXXxYYYxZZZ+OffX+OffY+OffZ+HomeX+HomeY+HomeZ" +msgstr "" +"Abmessungen der Bauplattform\n" +" & optional Versatz vom Ausgangspunkt\n" +"\n" +"Beispiel:\n" +" XXXxYYY\n" +" XXX,YYY,ZZZ\n" +" XXXxYYYxZZZ+OffX+OffY+OffZ" + +#: printrun/pronsole.py:495 +msgid "" +"Welcome to the printer console! Type \"help\" for a list of available " +"commands." +msgstr "" + +#: printrun/pronsole.py:657 printrun/pronsole.py:665 printrun/pronsole.py:673 +#: printrun/pronsole.py:1519 printrun/pronsole.py:1546 +#: printrun/pronsole.py:1618 printrun/pronterface.py:357 +#: printrun/pronterface.py:377 printrun/pronterface.py:394 +msgid "Printer is not online." +msgstr "Drucker ist nicht online." + +#: printrun/pronsole.py:692 +msgid "Exiting program. Goodbye!" +msgstr "" + +#: printrun/pronsole.py:697 +msgid "Disconnects from the printer and exits the program." +msgstr "" + +#: printrun/pronsole.py:974 +msgid "" +"load this file on startup instead of .pronsolerc ; you may chain config " +"files, if so settings auto-save will use the last specified file" +msgstr "" + +#: printrun/pronsole.py:975 +msgid "" +"executes command after configuration/.pronsolerc is loaded ; macros/settings " +"from these commands are not autosaved" +msgstr "" + +#: printrun/pronsole.py:976 +msgid "file to load" +msgstr "" + +#: printrun/pronsole.py:1104 +#, fuzzy +msgid "Loaded %s, %d lines." +msgstr "Geladen %s, %d Zeilen" + +#: printrun/pronsole.py:1105 printrun/pronterface.py:1381 +#, fuzzy +msgid "Estimated duration: %d layers, %s" +msgstr "Geschätze Dauer (pessimistisch): " + +#: printrun/pronsole.py:1134 +msgid "No file name given." +msgstr "" + +#: printrun/pronsole.py:1140 +msgid "Skeining file: %s" +msgstr "" + +#: printrun/pronsole.py:1142 printrun/pronterface.py:1305 +msgid "File not found!" +msgstr "Datei nicht gefunden!" + +#: printrun/pronsole.py:1147 +msgid "Entering slicer settings: %s" +msgstr "" + +#: printrun/pronsole.py:1151 +#, fuzzy +msgid "Slicing: " +msgstr "Slicing" + +#: printrun/pronsole.py:1158 +#, fuzzy +msgid "Loading sliced file." +msgstr "Datei laden" + +#: printrun/pronsole.py:1161 +#, fuzzy +msgid "Slicing failed: %s" +msgstr "Slicing" + +#: printrun/pronsole.py:1174 +msgid "" +"Creates a gcode file from an stl model using the slicer (with tab-completion)" +msgstr "" + +#: printrun/pronsole.py:1175 +msgid "slice filename.stl - create gcode file" +msgstr "" + +#: printrun/pronsole.py:1176 +msgid "" +"slice filename.stl view - create gcode file and view using skeiniso (if " +"using skeinforge)" +msgstr "" + +#: printrun/pronsole.py:1177 +#, fuzzy +msgid "slice set - adjust slicer settings" +msgstr " Slicing Einstellungen anpassen" + +#: printrun/pronsole.py:1189 +msgid "Please enter target name in 8.3 format." +msgstr "" + +#: printrun/pronsole.py:1195 +msgid "Uploading as %s" +msgstr "" + +#: printrun/pronsole.py:1196 +msgid "Uploading %s" +msgstr "" + +#: printrun/pronsole.py:1198 +msgid "Press Ctrl-C to interrupt upload." +msgstr "" + +#: printrun/pronsole.py:1201 +msgid "Progress: " +msgstr "" + +#: printrun/pronsole.py:1212 +msgid "Upload completed. %s should now be on the card." +msgstr "" + +#: printrun/pronsole.py:1216 +msgid "...interrupted!" +msgstr "" + +#: printrun/pronsole.py:1218 +msgid "Something wrong happened while uploading:" +msgstr "" + +#: printrun/pronsole.py:1224 +msgid "A partial file named %s may have been written to the sd card." +msgstr "" + +#: printrun/pronsole.py:1241 +msgid "" +"Send a loaded gcode file to the printer. Load a file with the load command " +"first." +msgstr "" + +#: printrun/pronsole.py:1243 +msgid "Send a loaded gcode file to the printer. You have %s loaded right now." +msgstr "" + +#: printrun/pronsole.py:1247 printrun/pronterface.py:730 +#: printrun/pronterface.py:1108 +msgid "No file loaded. Please use load first." +msgstr "Keine Datei geladen. Benutze zuerst laden." + +#: printrun/pronsole.py:1252 +#, fuzzy +msgid "Printing %s" +msgstr " Drucken:%04.2f %% |" + +#: printrun/pronsole.py:1253 +msgid "You can monitor the print with the monitor command." +msgstr "" + +#: printrun/pronsole.py:1261 +msgid "Not printing, cannot pause." +msgstr "" + +#: printrun/pronsole.py:1267 +#, fuzzy +msgid "Pauses a running print" +msgstr "Starte Druck" + +#: printrun/pronsole.py:1274 +msgid "Not paused, unable to resume. Start a print first." +msgstr "" + +#: printrun/pronsole.py:1284 +msgid "Resumes a paused print." +msgstr "" + +#: printrun/pronsole.py:1293 +msgid "Files on SD card:" +msgstr "" + +#: printrun/pronsole.py:1307 printrun/pronsole.py:1349 +#: printrun/pronsole.py:1562 +#, fuzzy +msgid "Printer is not online. Please connect to it first." +msgstr "Drucker ist nicht online." + +#: printrun/pronsole.py:1312 +msgid "Lists files on the SD card" +msgstr "" + +#: printrun/pronsole.py:1316 printrun/pronterface.py:1645 +msgid "Opening file failed." +msgstr "Datei öffnen fehlgeschlagen." + +#: printrun/pronsole.py:1322 printrun/pronterface.py:1651 +msgid "Starting print" +msgstr "Starte Druck" + +#: printrun/pronsole.py:1345 +#, fuzzy +msgid "Resets the printer." +msgstr "Drucker Verbinden" + +#: printrun/pronsole.py:1355 +#, fuzzy +msgid "File is not present on card. Please upload it first." +msgstr "Keine Datei geladen. Benutze zuerst laden." + +#: printrun/pronsole.py:1359 +msgid "Printing file: %s from SD card." +msgstr "" + +#: printrun/pronsole.py:1360 +msgid "Requesting SD print..." +msgstr "" + +#: printrun/pronsole.py:1364 +msgid "Print a file from the SD card. Tab completes with available file names." +msgstr "" + +#: printrun/pronsole.py:1365 +msgid "sdprint filename.g" +msgstr "" + +#: printrun/pronsole.py:1382 +msgid "Print resumed at: %s" +msgstr "" + +#: printrun/pronsole.py:1384 +msgid "Print started at: %s" +msgstr "" + +#: printrun/pronsole.py:1392 +msgid "Failed to inhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1399 +msgid "Failed to uninhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1403 +msgid "Print ended at: %(end_time)s and took %(duration)s" +msgstr "" + +#: printrun/pronsole.py:1457 +msgid "Printer is not currently printing. No ETA available." +msgstr "" + +#: printrun/pronsole.py:1460 +#, fuzzy +msgid "Est: %s of %s remaining" +msgstr " Erw: %s von %s verbleibend | " + +#: printrun/pronsole.py:1465 +msgid "Displays estimated remaining print time." +msgstr "" + +#: printrun/pronsole.py:1498 +#, fuzzy +msgid "Read the extruder and bed temperature." +msgstr "Sie müssen eine Temperatur eingeben." + +#: printrun/pronsole.py:1507 printrun/pronsole.py:1540 +msgid "You must enter a temperature." +msgstr "Sie müssen eine Temperatur eingeben." + +#: printrun/pronsole.py:1512 +msgid "" +"%s is a high temperature to set your extruder to. Are you sure you want to " +"do that?" +msgstr "" + +#: printrun/pronsole.py:1517 +#, fuzzy +msgid "Setting hotend temperature to %s degrees Celsius." +msgstr "Setze Hotend Temperatur auf %f Grad Celsius." + +#: printrun/pronsole.py:1521 printrun/pronterface.py:359 +msgid "" +"You cannot set negative temperatures. To turn the hotend off entirely, set " +"its temperature to 0." +msgstr "" +"Sie können keine negativen Temperaturen einstellen. Um das Hotend ganz " +"auszuschalten, Temperatur auf 0 setzen." + +#: printrun/pronsole.py:1524 +#, fuzzy +msgid "Sets the hotend temperature to the value entered." +msgstr "Setze Hotend Temperatur auf %f Grad Celsius." + +#: printrun/pronsole.py:1525 printrun/pronsole.py:1552 +msgid "Enter either a temperature in celsius or one of the following keywords" +msgstr "" + +#: printrun/pronsole.py:1544 +#, fuzzy +msgid "Setting bed temperature to %s degrees Celsius." +msgstr "Setze Heizbett Temperatur auf %f Grad Celsius." + +#: printrun/pronsole.py:1548 printrun/pronterface.py:379 +msgid "" +"You cannot set negative temperatures. To turn the bed off entirely, set its " +"temperature to 0." +msgstr "" +"Sie können keine negativen Temperaturen einstellen. Um das Heizbett ganz " +"auszuschalten, Temperatur auf 0 setzen." + +#: printrun/pronsole.py:1551 +#, fuzzy +msgid "Sets the bed temperature to the value entered." +msgstr "Setze Heizbett Temperatur auf %f Grad Celsius." + +#: printrun/pronsole.py:1565 +msgid "Printer is not printing. Please print something before monitoring." +msgstr "" + +#: printrun/pronsole.py:1567 +#, fuzzy +msgid "Monitoring printer, use ^C to interrupt." +msgstr "Überwache Drucker." + +#: printrun/pronsole.py:1572 printrun/pronterface.py:329 +msgid "Invalid period given." +msgstr "Ungültiger Abschnitt angegeben." + +#: printrun/pronsole.py:1573 +msgid "Updating values every %f seconds." +msgstr "" + +#: printrun/pronsole.py:1584 printrun/pronsole.py:1587 +msgid "Print progress: " +msgstr "" + +#: printrun/pronsole.py:1595 printrun/pronterface.py:333 +msgid "Done monitoring." +msgstr "Überwachung abgeschlossen." + +#: printrun/pronsole.py:1599 +msgid "Monitor a machine's temperatures and an SD print's status." +msgstr "" + +#: printrun/pronsole.py:1600 +msgid "" +"monitor - Reports temperature and SD print status (if SD printing) every 5 " +"seconds" +msgstr "" + +#: printrun/pronsole.py:1601 +msgid "" +"monitor 2 - Reports temperature and SD print status (if SD printing) every 2 " +"seconds" +msgstr "" + +#: printrun/pronsole.py:1612 +msgid "You must specify the tool index as an integer." +msgstr "" + +#: printrun/pronsole.py:1616 +msgid "Using tool %d." +msgstr "" + +#: printrun/pronsole.py:1620 +msgid "You cannot set negative tool numbers." +msgstr "" + +#: printrun/pronsole.py:1623 +msgid "" +"Switches to the specified tool (e.g. doing tool 1 will emit a T1 G-Code)." +msgstr "" + +#: printrun/pronsole.py:1627 +msgid "No move specified." +msgstr "" + +#: printrun/pronsole.py:1630 printrun/pronsole.py:1692 +#: printrun/pronsole.py:1735 printrun/pronsole.py:1762 +msgid "" +"Printer is currently printing. Please pause the print before you issue " +"manual commands." +msgstr "" + +#: printrun/pronsole.py:1633 printrun/pronsole.py:1759 +#, fuzzy +msgid "Printer is not online. Unable to move." +msgstr "Drucker ist nicht online." + +#: printrun/pronsole.py:1649 +msgid "Unknown axis." +msgstr "" + +#: printrun/pronsole.py:1654 +msgid "Invalid distance" +msgstr "" + +#: printrun/pronsole.py:1665 +msgid "Move an axis. Specify the name of the axis and the amount. " +msgstr "" + +#: printrun/pronsole.py:1666 +msgid "" +"move X 10 will move the X axis forward by 10mm at %s mm/min (default XY " +"speed)" +msgstr "" + +#: printrun/pronsole.py:1667 +msgid "move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min" +msgstr "" + +#: printrun/pronsole.py:1668 +msgid "" +"move Z -1 will move the Z axis down by 1mm at %s mm/min (default Z speed)" +msgstr "" + +#: printrun/pronsole.py:1669 +msgid "Common amounts are in the tabcomplete list." +msgstr "" + +#: printrun/pronsole.py:1699 printrun/pronsole.py:1742 +#, fuzzy +msgid "Invalid length given." +msgstr "Ungültiger Abschnitt angegeben." + +#: printrun/pronsole.py:1704 printrun/pronsole.py:1747 +#, fuzzy +msgid "Invalid speed given." +msgstr "Ungültiger Abschnitt angegeben." + +#: printrun/pronsole.py:1712 +msgid "Extruding %fmm of filament." +msgstr "" + +#: printrun/pronsole.py:1714 +msgid "Reversing %fmm of filament." +msgstr "" + +#: printrun/pronsole.py:1716 +msgid "Length is 0, not doing anything." +msgstr "" + +#: printrun/pronsole.py:1722 +msgid "" +"Extrudes a length of filament, 5mm by default, or the number of mm given as " +"a parameter" +msgstr "" + +#: printrun/pronsole.py:1723 +msgid "extrude - extrudes 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1724 +msgid "extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1725 +msgid "extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1726 +msgid "extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1732 +#, fuzzy +msgid "Printer is not online. Unable to reverse." +msgstr "Drucker ist nicht online." + +#: printrun/pronsole.py:1751 +msgid "" +"Reverses the extruder, 5mm by default, or the number of mm given as a " +"parameter" +msgstr "" + +#: printrun/pronsole.py:1752 +msgid "reverse - reverses 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1753 +msgid "reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1754 +msgid "reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1755 +msgid "reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1777 +#, fuzzy +msgid "Homes the printer" +msgstr "Drucker Verbinden" + +#: printrun/pronsole.py:1778 +msgid "home - homes all axes and zeroes the extruder(Using G28 and G92)" +msgstr "" + +#: printrun/pronsole.py:1779 +msgid "home xy - homes x and y axes (Using G28)" +msgstr "" + +#: printrun/pronsole.py:1780 +msgid "home z - homes z axis only (Using G28)" +msgstr "" + +#: printrun/pronsole.py:1781 +msgid "home e - set extruder position to zero (Using G92)" +msgstr "" + +#: printrun/pronsole.py:1782 +msgid "home xyze - homes all axes and zeroes the extruder (Using G28 and G92)" +msgstr "" + +#: printrun/pronsole.py:1790 +#, fuzzy +msgid "; Motors off" +msgstr "Motoren aus" + +#: printrun/pronsole.py:1792 +#, fuzzy +msgid "; Extruder off" +msgstr "Extrudieren" + +#: printrun/pronsole.py:1794 +msgid "; Heatbed off" +msgstr "" + +#: printrun/pronsole.py:1796 +msgid "; Fan off" +msgstr "" + +#: printrun/pronsole.py:1798 +msgid "; Power supply off" +msgstr "" + +#: printrun/pronsole.py:1801 +#, fuzzy +msgid "Printer is not online. Unable to turn it off." +msgstr "Drucker ist nicht online." + +#: printrun/pronsole.py:1804 +msgid "Turns off everything on the printer" +msgstr "" + +#: printrun/pronsole.py:1815 +msgid "G-Code calling host command \"%s\"" +msgstr "" + +#: printrun/pronsole.py:1824 +msgid "" +"Runs a custom script. Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronsole.py:1832 +msgid "" +"Runs a custom script which output gcode which will in turn be executed. " +"Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronterface.py:149 +msgid "Motors off" +msgstr "Motoren aus" + +#: printrun/pronterface.py:149 +msgid "Switch all motors off" +msgstr "" + +#: printrun/pronterface.py:150 +msgid "Advance extruder by set length" +msgstr "" + +#: printrun/pronterface.py:150 +msgid "Extrude" +msgstr "Extrudieren" + +#: printrun/pronterface.py:151 +msgid "Reverse" +msgstr "Rückwärts" + +#: printrun/pronterface.py:151 +msgid "Reverse extruder by set length" +msgstr "" + +#: printrun/pronterface.py:165 +#, fuzzy +msgid "Pronterface" +msgstr "Printer Interface" + +#: printrun/pronterface.py:192 +msgid "" +"# I moved all your custom buttons into .pronsolerc.\n" +"# Please don't add them here any more.\n" +"# Backup of your old buttons is in custombtn.old\n" +msgstr "" +"# Alle individuellen Buttons in .pronsolerc eingetragen.\n" +"# Bitte fügen Sie sie hier nicht mehr ein.\n" +"# Backup Ihrer alten Buttons befindet sich in custombtn.old\n" + +#: printrun/pronterface.py:197 +msgid "" +"Note!!! You have specified custom buttons in both custombtn.txt and ." +"pronsolerc" +msgstr "" +"Achtung! Sie haben benutzerdefinierte Buttons in custombtn.txt und ." +"pronsolerc angegeben" + +#: printrun/pronterface.py:198 +msgid "" +"Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt" +msgstr "" +"Ignoriere custombtn.txt. Alle aktuellen Buttons entfernen um wieder zu " +"custombtn.txt zurückzukehren" + +#: printrun/pronterface.py:331 +msgid "Monitoring printer." +msgstr "Überwache Drucker." + +#: printrun/pronterface.py:354 +msgid "Setting hotend temperature to %f degrees Celsius." +msgstr "Setze Hotend Temperatur auf %f Grad Celsius." + +#: printrun/pronterface.py:361 printrun/pronterface.py:381 +msgid "You must enter a temperature. (%s)" +msgstr "Sie müssen eine Temperatur eingeben. (%s)" + +#: printrun/pronterface.py:374 +msgid "Setting bed temperature to %f degrees Celsius." +msgstr "Setze Heizbett Temperatur auf %f Grad Celsius." + +#: printrun/pronterface.py:392 +msgid "Setting print speed factor to %d%%." +msgstr "" + +#: printrun/pronterface.py:396 +#, fuzzy +msgid "You must enter a speed. (%s)" +msgstr "Sie müssen eine Temperatur eingeben. (%s)" + +#: printrun/pronterface.py:470 +msgid "Plate function activated" +msgstr "" + +#: printrun/pronterface.py:479 +msgid "G-Code plate function activated" +msgstr "" + +#: printrun/pronterface.py:486 +msgid "Plated %s" +msgstr "" + +#: printrun/pronterface.py:500 +msgid "SD Upload" +msgstr "SD Laden" + +#: printrun/pronterface.py:504 +msgid "SD Print" +msgstr "SD Drucken" + +#: printrun/pronterface.py:565 +msgid "" +"Manual move outside of the build volume prevented (see the \"Clamp manual " +"moves\" option)." +msgstr "" + +#: printrun/pronterface.py:621 +msgid "" +"Attempted to write invalid text to console, which could be due to an invalid " +"baudrate" +msgstr "" + +#: printrun/pronterface.py:655 +msgid " Opens file" +msgstr " Öffnet eine Datei" + +#: printrun/pronterface.py:655 +msgid "&Open..." +msgstr "&Öffnen..." + +#: printrun/pronterface.py:663 +msgid " Clear output console" +msgstr " Ausgabe Konsole leeren" + +#: printrun/pronterface.py:663 +msgid "Clear console" +msgstr "Konsole leeren" + +#: printrun/pronterface.py:664 +msgid " Closes the Window" +msgstr " Schliesst das Fenster" + +#: printrun/pronterface.py:664 +msgid "E&xit" +msgstr "&Verlassen" + +#: printrun/pronterface.py:665 +msgid "&File" +msgstr "&Datei" + +#: printrun/pronterface.py:668 +msgid " Edit open file" +msgstr " Offene Datei bearbeiten" + +#: printrun/pronterface.py:668 +msgid "&Edit..." +msgstr "&Bearbeiten..." + +#: printrun/pronterface.py:669 +msgid " Compose 3D models into a single plate" +msgstr "" + +#: printrun/pronterface.py:669 +#, fuzzy +msgid "Plater" +msgstr "Heizelement:" + +#: printrun/pronterface.py:670 +msgid " Compose G-Codes into a single plate" +msgstr "" + +#: printrun/pronterface.py:670 +msgid "G-Code Plater" +msgstr "" + +#: printrun/pronterface.py:671 +msgid " Exclude parts of the bed from being printed" +msgstr "" + +#: printrun/pronterface.py:671 +msgid "Excluder" +msgstr "" + +#: printrun/pronterface.py:672 +msgid " Project slices" +msgstr " Projekt Slices" + +#: printrun/pronterface.py:672 +msgid "Projector" +msgstr "Projektor" + +#: printrun/pronterface.py:673 +msgid "&Tools" +msgstr "" + +#: printrun/pronterface.py:676 +msgid "" +" Recover previous print after a disconnect (homes X, Y, restores Z and E " +"status)" +msgstr "" + +#: printrun/pronterface.py:676 +msgid "Recover" +msgstr "" + +#: printrun/pronterface.py:679 +msgid "&Advanced" +msgstr "" + +#: printrun/pronterface.py:686 +#, fuzzy +msgid "Print &settings" +msgstr "Einstellungen bearbeiten" + +#: printrun/pronterface.py:687 +#, fuzzy +msgid "&Filament" +msgstr "&Datei" + +#: printrun/pronterface.py:688 +#, fuzzy +msgid "&Printer" +msgstr "Drucken" + +#: printrun/pronterface.py:694 +msgid "&Slic3r" +msgstr "" + +#: printrun/pronterface.py:696 +msgid "Failed to load Slic3r configuration:" +msgstr "" + +#: printrun/pronterface.py:702 +msgid "&Macros" +msgstr "&Makros" + +#: printrun/pronterface.py:703 +msgid "<&New...>" +msgstr "<&Neu...>" + +#: printrun/pronterface.py:704 +msgid " Options dialog" +msgstr " Optionen Dialog" + +#: printrun/pronterface.py:704 +msgid "&Options" +msgstr "&Optionen" + +#: printrun/pronterface.py:706 +msgid " Adjust slicing settings" +msgstr " Slicing Einstellungen anpassen" + +#: printrun/pronterface.py:706 +#, fuzzy +msgid "Slicing settings" +msgstr "Slicing Einstellungen" + +#: printrun/pronterface.py:708 +msgid "Debug communications" +msgstr "" + +#: printrun/pronterface.py:709 +msgid "Print all G-code sent to and received from the printer." +msgstr "" + +#: printrun/pronterface.py:713 +msgid "&Settings" +msgstr "&Einstellungen" + +#: printrun/pronterface.py:719 +#, fuzzy +msgid "&About Printrun" +msgstr "Drucker überwachen" + +#: printrun/pronterface.py:719 +msgid "Show about dialog" +msgstr "" + +#: printrun/pronterface.py:720 +msgid "&Help" +msgstr "" + +#: printrun/pronterface.py:746 +msgid "" +"Printrun is a pure Python 3D printing (and other types of CNC) host software." +msgstr "" + +#: printrun/pronterface.py:750 +#, fuzzy +msgid "%.02fmm of filament have been extruded during prints" +msgstr "mm Filament in Druck genutzt\n" + +#: printrun/pronterface.py:781 +#, fuzzy +msgid "Monitor printer status" +msgstr "Drucker überwachen" + +#: printrun/pronterface.py:781 +msgid "" +"Regularly monitor printer temperatures (required to have functional " +"temperature graph or gauges)" +msgstr "" + +#: printrun/pronterface.py:782 +msgid "Path to the simarrange binary to use in the STL plater" +msgstr "" + +#: printrun/pronterface.py:782 +msgid "Simarrange command" +msgstr "" + +#: printrun/pronterface.py:783 +msgid "Circular build platform" +msgstr "" + +#: printrun/pronterface.py:783 +msgid "Draw a circular (or oval) build platform instead of a rectangular one" +msgstr "" + +#: printrun/pronterface.py:784 +#, fuzzy +msgid "Extruders count" +msgstr "Extrudieren" + +#: printrun/pronterface.py:784 +msgid "Number of extruders" +msgstr "" + +#: printrun/pronterface.py:785 +msgid "Clamp manual moves" +msgstr "" + +#: printrun/pronterface.py:785 +msgid "Prevent manual moves from leaving the specified build dimensions" +msgstr "" + +#: printrun/pronterface.py:786 +#, fuzzy +msgid "Interface mode" +msgstr "Makro Name eingeben" + +#: printrun/pronterface.py:786 +msgid "" +"Standard interface is a one-page, three columns layout with controls/" +"visualization/log\n" +"Compact mode is a one-page, two columns layout with controls + log/" +"visualization\n" +"Tabbed mode is a two-pages mode, where the first page shows controls and the " +"second one shows visualization and log." +msgstr "" + +#: printrun/pronterface.py:787 +msgid "Controls mode" +msgstr "" + +#: printrun/pronterface.py:787 +msgid "" +"Standard controls include all controls needed for printer setup and " +"calibration, while Mini controls are limited to the ones needed for daily " +"printing" +msgstr "" + +#: printrun/pronterface.py:788 +msgid "Add a menu to select Slic3r profiles directly from Pronterface" +msgstr "" + +#: printrun/pronterface.py:788 +msgid "Enable Slic3r integration" +msgstr "" + +#: printrun/pronterface.py:789 +msgid "Update Slic3r default presets" +msgstr "" + +#: printrun/pronterface.py:789 +msgid "" +"When selecting a profile in Slic3r integration menu, also save it as the " +"default Slic3r preset" +msgstr "" + +#: printrun/pronterface.py:790 +msgid "Main visualization" +msgstr "" + +#: printrun/pronterface.py:790 +msgid "Select visualization for main window." +msgstr "" + +#: printrun/pronterface.py:791 +msgid "Use 3D in GCode viewer window" +msgstr "" + +#: printrun/pronterface.py:791 +msgid "Use 3D mode instead of 2D layered mode in the visualization window" +msgstr "" + +#: printrun/pronterface.py:792 +msgid "Use a lighter 3D visualization" +msgstr "" + +#: printrun/pronterface.py:792 +msgid "" +"Use a lighter visualization with simple lines instead of extruded paths for " +"3D viewer" +msgstr "" + +#: printrun/pronterface.py:793 +msgid "Track current layer in main 3D view" +msgstr "" + +#: printrun/pronterface.py:793 +msgid "Track the currently printing layer in the main 3D visualization" +msgstr "" + +#: printrun/pronterface.py:794 +msgid "Display temperature graph" +msgstr "" + +#: printrun/pronterface.py:794 +msgid "Display time-lapse temperature graph" +msgstr "" + +#: printrun/pronterface.py:795 +msgid "Display graphical gauges for temperatures visualization" +msgstr "" + +#: printrun/pronterface.py:795 +msgid "Display temperature gauges" +msgstr "" + +#: printrun/pronterface.py:796 +msgid "Display a checkbox that, when check, locks most of Pronterface" +msgstr "" + +#: printrun/pronterface.py:796 +msgid "Display interface lock checkbox" +msgstr "" + +#: printrun/pronterface.py:797 +msgid "If lock checkbox is enabled, lock the interface when starting a print" +msgstr "" + +#: printrun/pronterface.py:797 +msgid "Lock interface upon print start" +msgstr "" + +#: printrun/pronterface.py:804 +msgid "Preview extrusion width" +msgstr "" + +#: printrun/pronterface.py:804 +#, fuzzy +msgid "Width of Extrusion in Preview" +msgstr "Vorschaubreite der Extrudierung (Vorgabe: 0.5)" + +#: printrun/pronterface.py:805 +#, fuzzy +msgid "Fine Grid Spacing" +msgstr "Feiner Rasterabstand (Vorgabe: 10)" + +#: printrun/pronterface.py:805 +#, fuzzy +msgid "Fine grid spacing" +msgstr "Feiner Rasterabstand (Vorgabe: 10)" + +#: printrun/pronterface.py:806 +#, fuzzy +msgid "Coarse Grid Spacing" +msgstr "Grober Rasterabstand (Vorgabe: 50)" + +#: printrun/pronterface.py:806 +#, fuzzy +msgid "Coarse grid spacing" +msgstr "Grober Rasterabstand (Vorgabe: 50)" + +#: printrun/pronterface.py:807 +msgid "Background color" +msgstr "" + +#: printrun/pronterface.py:807 +#, fuzzy +msgid "Pronterface background color" +msgstr "Pronterface Hintergrundfarbe (Vorgabe: #FFFFFF)" + +#: printrun/pronterface.py:808 +msgid "3D view background color" +msgstr "" + +#: printrun/pronterface.py:808 +msgid "Color of the 3D view background" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "3D view travel moves color" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "Color of travel moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "3D view print moves color" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "Color of print moves with tool 0 in 3D view" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "3D view tool 1 moves color" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "Color of print moves with tool 1 in 3D view" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "3D view printed moves color" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "Color of printed moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "3D view current layer moves color" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "Color of moves in current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "3D view printed current layer moves color" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "Color of already printed moves from current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:815 +msgid "Changing most settings here will require restart to get effect" +msgstr "" + +#: printrun/pronterface.py:815 +msgid "Note:" +msgstr "" + +#: printrun/pronterface.py:822 +msgid "automatically try to connect to printer on startup" +msgstr "" + +#: printrun/pronterface.py:835 printrun/pronterface.py:1316 +msgid "Failed to load recent files list:" +msgstr "" + +#: printrun/pronterface.py:916 +#, fuzzy +msgid "SD upload: %04.2f%% |" +msgstr "SD Drucken:%04.2f %%" + +#: printrun/pronterface.py:917 printrun/pronterface.py:922 +msgid " Line# %d of %d lines |" +msgstr " Zeile# %d von %d Zeilen |" + +#: printrun/pronterface.py:919 +#, fuzzy +msgid "SD printing: %04.2f%% |" +msgstr "SD Drucken:%04.2f %%" + +#: printrun/pronterface.py:921 +#, fuzzy +msgid "Printing: %04.2f%% |" +msgstr " Drucken:%04.2f %% |" + +#: printrun/pronterface.py:924 +msgid " Est: %s of %s remaining | " +msgstr " Erw: %s von %s verbleibend | " + +#: printrun/pronterface.py:926 +#, fuzzy +msgid " Z: %.3f mm" +msgstr " Z: %0.2f mm" + +#: printrun/pronterface.py:931 +msgid "Disconnecting after 4 failed writes." +msgstr "" + +#: printrun/pronterface.py:972 +#, fuzzy +msgid "Locking interface." +msgstr "Printer Interface" + +#: printrun/pronterface.py:976 +#, fuzzy +msgid "Unlocking interface." +msgstr "Überwache Drucker." + +#: printrun/pronterface.py:985 +msgid "Connecting..." +msgstr "Verbinde..." + +#: printrun/pronterface.py:997 +msgid "Could not parse baud rate: " +msgstr "" + +#: printrun/pronterface.py:1013 printrun/pronterface.py:1023 +msgid "Error: You are trying to connect to a non-existing port." +msgstr "" + +#: printrun/pronterface.py:1015 +msgid "Error: You don't have permission to open %s." +msgstr "" + +#: printrun/pronterface.py:1016 +msgid "You might need to add yourself to the dialout group." +msgstr "" + +#: printrun/pronterface.py:1043 +msgid "Disconnected." +msgstr "Getrennt." + +#: printrun/pronterface.py:1071 +msgid "Reset." +msgstr "Zurücksetzen." + +#: printrun/pronterface.py:1072 +msgid "Are you sure you want to reset the printer?" +msgstr "Möchten Sie den Drucker wirklich zurücksetzen?" + +#: printrun/pronterface.py:1072 +msgid "Reset?" +msgstr "Zurücksetzen?" + +#: printrun/pronterface.py:1093 +msgid "Restart" +msgstr "Neustart" + +#: printrun/pronterface.py:1126 +msgid "Pick SD filename" +msgstr "Wähle SD Dateiname" + +#: printrun/pronterface.py:1144 +msgid "File upload complete" +msgstr "Datei Upload komplett" + +#: printrun/pronterface.py:1151 +msgid "Print paused at: %s" +msgstr "" + +#: printrun/pronterface.py:1163 +msgid "Resume" +msgstr "Fortsetzen" + +#: printrun/pronterface.py:1166 +#, fuzzy +msgid "Resuming." +msgstr "Fortsetzen" + +#: printrun/pronterface.py:1192 +msgid "Pick SD file" +msgstr "Wähle SD Datei" + +#: printrun/pronterface.py:1192 +msgid "Select the file to print" +msgstr "Wähle Druckdatei" + +#: printrun/pronterface.py:1228 printrun/pronterface.py:1259 +msgid "Slicing " +msgstr "Slicing" + +#: printrun/pronterface.py:1237 +msgid "Failed to execute slicing software: " +msgstr "Fehler beim Ausführen der Slicing Software:" + +#: printrun/pronterface.py:1244 +msgid "Slicing..." +msgstr "Slicing..." + +#: printrun/pronterface.py:1296 +msgid "Open file to print" +msgstr "Öffne zu druckende Datei" + +#: printrun/pronterface.py:1297 +#, fuzzy +msgid "" +"OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*." +"gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*" +msgstr "" +"OBJ,STL und GCODE Dateien (;*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ;)" + +#: printrun/pronterface.py:1325 +msgid "Could not update recent files list:" +msgstr "" + +#: printrun/pronterface.py:1359 +msgid "Loaded %s, %d lines" +msgstr "Geladen %s, %d Zeilen" + +#: printrun/pronterface.py:1362 +msgid "Load File" +msgstr "Datei laden" + +#: printrun/pronterface.py:1376 +#, fuzzy +msgid "%.2fmm of filament used in this print" +msgstr "mm Filament in Druck genutzt\n" + +#: printrun/pronterface.py:1377 +msgid "The print goes:" +msgstr "" + +#: printrun/pronterface.py:1378 +#, fuzzy +msgid "- from %.2f mm to %.2f mm in X and is %.2f mm wide" +msgstr "" +"Der Druck verläuft von %f mm bis %f mm in X\n" +"und ist %f mm breit\n" + +#: printrun/pronterface.py:1379 +#, fuzzy +msgid "- from %.2f mm to %.2f mm in Y and is %.2f mm deep" +msgstr "" +"Der Druck verläuft von %f mm bis %f mm in Y\n" +"und ist %f mm breit\n" + +#: printrun/pronterface.py:1380 +#, fuzzy +msgid "- from %.2f mm to %.2f mm in Z and is %.2f mm high" +msgstr "" +"Der Druck verläuft von %f mm bis %f mm in Z\n" +"und ist %f mm hoch\n" + +#: printrun/pronterface.py:1450 +msgid "Printer is now online." +msgstr "Drucker ist jetzt Online." + +#: printrun/pronterface.py:1455 +msgid "Disconnect" +msgstr "Trennen" + +#: printrun/pronterface.py:1688 +msgid "click to add new custom button" +msgstr "Individuellen Button hinzufügen" + +#: printrun/pronterface.py:1694 +msgid "Execute command: " +msgstr "Kommando ausführen:" + +#: printrun/pronterface.py:1714 +msgid "" +"Defines custom button. Usage: button \"title\" [/c \"colour\"] command" +msgstr "" +"Definiert einen individuellen Button. Nutzung: button \"title\" [/c " +"\"colour\"] command" + +#: printrun/pronterface.py:1736 +msgid "Custom button number should be between 0 and 63" +msgstr "Nummer des individuellen Button sollte zwischen 0 und 63 sein." + +#: printrun/pronterface.py:1827 +msgid "Edit custom button '%s'" +msgstr "Individuellen Button '%s' bearbeiten" + +#: printrun/pronterface.py:1829 +msgid "Move left <<" +msgstr "Links bewegen <<" + +#: printrun/pronterface.py:1832 +msgid "Move right >>" +msgstr "Rechts bewegen >>" + +#: printrun/pronterface.py:1836 +msgid "Remove custom button '%s'" +msgstr "Individuellen Button '%s' entfernen" + +#: printrun/pronterface.py:1839 +msgid "Add custom button" +msgstr "Individuellen Button hinzufuegen" + +#: printrun/pronterface.py:1972 +msgid "event object missing" +msgstr "Ereigniss Objekt fehlt" + +#: printrun/pronterface.py:1985 +msgid "Do you want to erase the macro?" +msgstr "Möchten Sie das Makro löschen?" + +#: printrun/pronterface.py:1989 +msgid "Cancelled." +msgstr "Abgebrochen." + +#: printrun/pronterface.py:2007 +msgid "Enter macro name" +msgstr "Makro Name eingeben" + +#: printrun/pronterface.py:2010 +msgid "Macro name:" +msgstr "Makro Name:" + +#: printrun/pronterface.py:2013 +msgid "Ok" +msgstr "Ok" + +#: printrun/pronterface.py:2035 +#, fuzzy +msgid "Macro name may contain only ASCII alphanumeric symbols and underscores" +msgstr "Makro Name darf nur alphanumerische Zeichen und Unterstriche enthalten" + +#: printrun/pronterface.py:2038 +msgid "Name '%s' is being used by built-in command" +msgstr "Name '%s' wird durch eingebautes Kommando genutzt" + +#: pronsole.py:31 +msgid "Caught an exception, exiting:" +msgstr "" + +#~ msgid "Folder of last opened file" +#~ msgstr "Verzeichniss der zuletzt geöffneten Datei" + +#~ msgid "Check temp" +#~ msgstr "Temperatur prüfen" + +#~ msgid "Mini mode" +#~ msgstr "Mini-Modus" + +#~ msgid "Compose" +#~ msgstr "Zusammenstellen" + +#~ msgid "Z:" +#~ msgstr "Z:" + +#~ msgid "Full mode" +#~ msgstr "Voll-Modus" + +#~ msgid "Printer is online. " +#~ msgstr "Drucker ist online. " + +#~ msgid "Bed" +#~ msgstr "Heizbett" + +#~ msgid "Hotend" +#~ msgstr "Hotend" + +#~ msgid ", %d lines" +#~ msgstr ", %d Zeilen" + +#~ msgid "Paused." +#~ msgstr "Pausiert." + +#~ msgid "" +#~ "Slice command\n" +#~ " default:\n" +#~ " python skeinforge/skeinforge_application/skeinforge_utilities/" +#~ "skeinforge_craft.py $s)" +#~ msgstr "" +#~ "Kommando Slicing\n" +#~ " Vorgabe:\n" +#~ " python skeinforge/skeinforge_application/skeinforge_utilities/" +#~ "skeinforge_craft.py $s)" + +#~ msgid "" +#~ "Slice settings command\n" +#~ " default:\n" +#~ " python skeinforge/skeinforge_application/skeinforge.py" +#~ msgstr "" +#~ "Kommando Slicing Einstellungen\n" +#~ " Vorgabe:\n" +#~ " python skeinforge/skeinforge_application/skeinforge.py" diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/fr/LC_MESSAGES/plater.mo Binary file printrun-src/locale/fr/LC_MESSAGES/plater.mo has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/fr/LC_MESSAGES/plater.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/fr/LC_MESSAGES/plater.po Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,95 @@ +# French Plater Message Catalog +# Copyright (C) 2012 Guillaume Seguin +# Guillaume Seguin , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: Plater\n" +"POT-Creation-Date: 2012-08-04 21:53+CEST\n" +"PO-Revision-Date: 2012-02-26 02:41+0100\n" +"Last-Translator: Guillaume Seguin \n" +"Language-Team: FR \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + +#: plater.py:246 +msgid "Plate building tool" +msgstr "Outil d'assemblage de plateau" + +#: plater.py:252 +msgid "Clear" +msgstr "Vider" + +#: plater.py:253 +msgid "Load" +msgstr "Charger" + +#: plater.py:255 plater.py:258 +msgid "Export" +msgstr "Exporter" + +#: plater.py:260 +msgid "Done" +msgstr "Terminé" + +#: plater.py:262 +msgid "Cancel" +msgstr "Annuler" + +#: plater.py:264 +msgid "Snap to Z = 0" +msgstr "Poser en Z = 0" + +#: plater.py:265 +msgid "Put at 100, 100" +msgstr "Placer en 100, 100" + +#: plater.py:266 +msgid "Delete" +msgstr "Supprimer" + +#: plater.py:267 +msgid "Auto" +msgstr "Auto" + +#: plater.py:291 +msgid "Autoplating" +msgstr "Placement auto" + +#: plater.py:319 +msgid "Bed full, sorry sir :(" +msgstr "Le lit est plein, désolé :(" + +#: plater.py:329 +msgid "" +"Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "" +"Êtes vous sur de vouloir vider la grille ? Toutes les modifications non " +"enregistrées seront perdues." + +#: plater.py:329 +msgid "Clear the grid?" +msgstr "Vider la grille ?" + +#: plater.py:371 +msgid "Pick file to save to" +msgstr "Choisir le fichier dans lequel enregistrer" + +#: plater.py:372 +msgid "STL files (;*.stl;*.STL;)" +msgstr "Fichiers STL (;*.stl;*.STL;)" + +#: plater.py:393 +msgid "wrote %s" +msgstr "%s écrit" + +#: plater.py:396 +msgid "Pick file to load" +msgstr "Choisir le fichier à charger" + +#: plater.py:397 +msgid "STL files (;*.stl;*.STL;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" +msgstr "Fichiers STL (;*.stl;*.STL;)|*.stl|Fichiers OpenSCAD (;*.scad;)|*.scad" diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/fr/LC_MESSAGES/pronterface.mo Binary file printrun-src/locale/fr/LC_MESSAGES/pronterface.mo has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/fr/LC_MESSAGES/pronterface.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/fr/LC_MESSAGES/pronterface.po Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,2251 @@ +# Pronterface Message Catalog Template +# Copyright (C) 2011 Jonathan Marsden +# Jonathan Marsden , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: Pronterface\n" +"POT-Creation-Date: 2014-03-30 14:35+CEST\n" +"PO-Revision-Date: 2013-11-02 13:58+0100\n" +"Last-Translator: Guillaume Seguin \n" +"Language-Team: FR \n" +"Language: Français\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"X-Generator: Poedit 1.5.7\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: printrun/excluder.py:26 +msgid "" +"Part excluder: draw rectangles where print instructions should be ignored" +msgstr "" +"Exclueur de pièce: dessiner des rectangles là où les instructions " +"d'impressions doivent être ignorées" + +#: printrun/excluder.py:27 printrun/excluder.py:29 +msgid "Reset selection" +msgstr "Remettre à zéro la sélection" + +#: printrun/gcodeplater.py:43 printrun/gcodeplater.py:44 +msgid "GCODE files (*.gcode;*.GCODE;*.g)" +msgstr "Fichiers GCODE (*.gcode;*.GCODE;*.g)" + +#: printrun/gcodeplater.py:112 printrun/gcodeplater.py:152 +msgid "Warning: no rotation support for now, object won't be correctly rotated" +msgstr "" +"Attention: les rotations ne sont pas supportées pour le moment, l'objet ne " +"sera pas correctement tourné" + +#: printrun/gcodeplater.py:141 printrun/gcodeplater.py:178 +msgid "Exported merged G-Codes to %s" +msgstr "G-Codes fusionnés exportés dans %s" + +#: printrun/gcview.py:352 +msgid "Fit to plate" +msgstr "Adapter au plateau" + +#: printrun/gcview.py:353 +msgid "Fit to plate [F]" +msgstr "Adapter au plateau [F]" + +#: printrun/gui/__init__.py:19 printrun/pronterface.py:45 +msgid "WX is not installed. This program requires WX to run." +msgstr "" +"wxWidgets n'est pas installé. Ce programme nécessite la librairie wxWidgets " +"pour fonctionner." + +#: printrun/gui/__init__.py:159 +msgid "Commands" +msgstr "Commandes" + +#: printrun/gui/__init__.py:160 +msgid "Status" +msgstr "État" + +#: printrun/gui/controls.py:126 +msgid "Heat:" +msgstr "Buse:" + +#: printrun/gui/controls.py:129 +msgid "Switch Hotend Off" +msgstr "Éteindre la tête chauffante" + +#: printrun/gui/controls.py:129 printrun/gui/controls.py:149 +#: printrun/gui/toolbar.py:85 +msgid "Off" +msgstr "Off" + +#: printrun/gui/controls.py:137 +msgid "Select Temperature for Hotend" +msgstr "Sélectionner la température de la buse" + +#: printrun/gui/controls.py:141 +msgid "Switch Hotend On" +msgstr "Allumer la tête chauffante" + +#: printrun/gui/controls.py:141 printrun/gui/controls.py:161 +#: printrun/gui/controls.py:198 +msgid "Set" +msgstr "Régler" + +#: printrun/gui/controls.py:146 printrun/gui/controls.py:215 +msgid "Bed:" +msgstr "Plateau :" + +#: printrun/gui/controls.py:149 +msgid "Switch Heated Bed Off" +msgstr "Éteindre le plateau chauffant" + +#: printrun/gui/controls.py:157 +msgid "Select Temperature for Heated Bed" +msgstr "Sélectionner la température du plateau" + +#: printrun/gui/controls.py:161 +msgid "Switch Heated Bed On" +msgstr "Allumer le plateau chauffant" + +#: printrun/gui/controls.py:187 +#, fuzzy +msgid "Print speed:" +msgstr "Impression interrompue à : %s" + +#: printrun/gui/controls.py:192 printrun/gui/controls.py:207 +msgid "%d%%" +msgstr "" + +#: printrun/gui/controls.py:198 +msgid "Set print speed factor" +msgstr "" + +#: printrun/gui/controls.py:213 +msgid "Heater:" +msgstr "Buse:" + +#: printrun/gui/controls.py:265 +msgid "Length:" +msgstr "" + +#: printrun/gui/controls.py:267 +msgid "mm" +msgstr "mm" + +#: printrun/gui/controls.py:267 +msgid "mm @" +msgstr "mm @" + +#: printrun/gui/controls.py:269 +msgid "Amount to Extrude or Retract (mm)" +msgstr "Longueur à extruder ou inverser (mm)" + +#: printrun/gui/controls.py:272 +msgid "Extrude / Retract speed (mm/min)" +msgstr "Vitesse d'extrusion/inversion (mm/min)" + +#: printrun/gui/controls.py:278 +msgid "Speed:" +msgstr "" + +#: printrun/gui/controls.py:279 +msgid "" +"mm/\n" +"min" +msgstr "" +"mm/\n" +"min" + +#: printrun/gui/controls.py:290 +msgid "Tool:" +msgstr "Outil:" + +#: printrun/gui/controls.py:293 +msgid "Click to switch current extruder" +msgstr "Cliquer pour changer l'extrudeur actif" + +#: printrun/gui/controls.py:310 +msgid "Select current extruder" +msgstr "Sélectionner l'extrudeur actif" + +#: printrun/gui/controls.py:371 +msgid "Set Maximum Speed for X & Y axes (mm/min)" +msgstr "Définir la vitesse maximale pour les axes X & Y (mm/min)" + +#: printrun/gui/controls.py:372 +msgid "XY:" +msgstr "XY:" + +#: printrun/gui/controls.py:374 +msgid "mm/min Z:" +msgstr "mm/min Z:" + +#: printrun/gui/controls.py:376 +msgid "Set Maximum Speed for Z axis (mm/min)" +msgstr "Définir la vitesse maximale pour l'axe Z (mm/min)" + +#: printrun/gui/graph.py:28 +msgid "Temperature graph" +msgstr "Graphe de température" + +#: printrun/gui/log.py:32 +msgid "" +"Send commands to printer\n" +"(Type 'help' for simple\n" +"help function)" +msgstr "" +"Envoyer des commandes à l'imprimante\n" +"(Taper 'help' pour avoir de l'aide)" + +#: printrun/gui/log.py:39 +msgid "Send" +msgstr "Envoyer" + +#: printrun/gui/log.py:39 +msgid "Send Command to Printer" +msgstr "Envoyer une commande à l'imprimante" + +#: printrun/gui/toolbar.py:23 +msgid "Lock" +msgstr "Verrouiller" + +#: printrun/gui/toolbar.py:25 +msgid "Lock graphical interface" +msgstr "Verrouiller l'interface graphique" + +#: printrun/gui/toolbar.py:32 +msgid "" +"Communication Settings\n" +"Click to rescan ports" +msgstr "" +"Paramètres de communication\n" +"Cliquer pour rescanner les ports" + +#: printrun/gui/toolbar.py:32 +msgid "Port" +msgstr "Port" + +#: printrun/gui/toolbar.py:37 +msgid "Select Port Printer is connected to" +msgstr "Sélectionner le port auquel l'imprimante est connectée" + +#: printrun/gui/toolbar.py:46 +msgid "Select Baud rate for printer communication" +msgstr "Sélectionner la vitesse de communication" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1052 +msgid "Connect" +msgstr "Connecter" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1053 +msgid "Connect to the printer" +msgstr "Se connecter à l'imprimante" + +#: printrun/gui/toolbar.py:61 +msgid "Reset" +msgstr "Réinitialiser" + +#: printrun/gui/toolbar.py:61 +msgid "Reset the printer" +msgstr "Remettre à zéro l'imprimante" + +#: printrun/gui/toolbar.py:69 +msgid "Load a 3D model file" +msgstr "Charger un modèle 3D" + +#: printrun/gui/toolbar.py:69 +msgid "Load file" +msgstr "Charger un fichier" + +#: printrun/gui/toolbar.py:70 +msgid "SD" +msgstr "SD" + +#: printrun/gui/toolbar.py:70 +msgid "SD Card Printing" +msgstr "Impression depuis une carte SD" + +#: printrun/gui/toolbar.py:74 +msgid "Start Printing Loaded File" +msgstr "Commencer l'impression du fichier chargé" + +#: printrun/gui/toolbar.py:74 printrun/pronterface.py:1003 +#: printrun/pronterface.py:1062 printrun/pronterface.py:1078 +#: printrun/pronterface.py:1363 printrun/pronterface.py:1445 +msgid "Print" +msgstr "Imprimer" + +#: printrun/gui/toolbar.py:80 +msgid "Pause Current Print" +msgstr "Mettre en pause l'impression" + +#: printrun/gui/toolbar.py:80 printrun/pronterface.py:1002 +#: printrun/pronterface.py:1061 printrun/pronterface.py:1081 +#: printrun/pronterface.py:1091 printrun/pronterface.py:1172 +#: printrun/pronterface.py:1364 +msgid "Pause" +msgstr "Pause" + +#: printrun/gui/toolbar.py:85 +msgid "Turn printer off" +msgstr "Éteindre l'imprimante" + +#: printrun/gui/viz.py:65 +msgid "" +"Click to examine / edit\n" +" layers of loaded file" +msgstr "Cliquer pour examiner / éditer les couches du fichier chargé" + +#: printrun/gui/widgets.py:36 +msgid "Find" +msgstr "Trouver" + +#: printrun/gui/widgets.py:38 +msgid "Save" +msgstr "Enregistrer" + +#: printrun/gui/widgets.py:43 printrun/objectplater.py:76 +#: printrun/pronterface.py:1257 printrun/pronterface.py:2017 +msgid "Cancel" +msgstr "Annuler" + +#: printrun/gui/widgets.py:70 +msgid "Not Found!" +msgstr "Non trouvé !" + +#: printrun/gui/widgets.py:124 +msgid "Printer settings" +msgstr "Paramètres de l'imprimante" + +#: printrun/gui/widgets.py:125 +msgid "User interface" +msgstr "Interface utilisateur" + +#: printrun/gui/widgets.py:126 +#, fuzzy +msgid "Colors" +msgstr "Couleur" + +#: printrun/gui/widgets.py:127 +msgid "External commands" +msgstr "Commandes externes" + +#: printrun/gui/widgets.py:132 +msgid "Edit settings" +msgstr "Modifier les paramètres" + +#: printrun/gui/widgets.py:135 +msgid "Settings" +msgstr "Paramètres" + +#: printrun/gui/widgets.py:187 +msgid "Custom button" +msgstr "Commande personnalisée" + +#: printrun/gui/widgets.py:193 +msgid "Button title" +msgstr "Titre du bouton" + +#: printrun/gui/widgets.py:196 +msgid "Command" +msgstr "Commande" + +#: printrun/gui/widgets.py:205 +msgid "Color" +msgstr "Couleur" + +#: printrun/gviz.py:29 +msgid "Layer number and Z position show here when you scroll" +msgstr "" +"Le numéro de couche et la position en Z appairassent ici quand vous faites " +"défiler les couches" + +#: printrun/gviz.py:39 +msgid "Zoom In [+]" +msgstr "Zoomer [+]" + +#: printrun/gviz.py:40 +msgid "Zoom Out [-]" +msgstr "Dézoomer [-]" + +#: printrun/gviz.py:42 +msgid "Move Up a Layer [U]" +msgstr "Voir la couche du dessus [U]" + +#: printrun/gviz.py:43 +msgid "Move Down a Layer [D]" +msgstr "Voir la couche du dessous [D]" + +#: printrun/gviz.py:44 +msgid "Reset view" +msgstr "Réinitialiser la vue" + +#: printrun/gviz.py:68 +msgid "Gcode view, shift to move view, mousewheel to set layer" +msgstr "" +"Visualisation du Gcode, utiliser Maj pour déplacer la vue, la roulette pour " +"changer de couche" + +#: printrun/gviz.py:105 printrun/gviz.py:241 +msgid "Layer %d - Going Up - Z = %.03f mm" +msgstr "Couche %d - En montée - Z = %.03f mm" + +#: printrun/gviz.py:250 +msgid "Layer %d - Going Down - Z = %.03f mm" +msgstr "Couche %d - En descente - Z = %.03f mm" + +#: printrun/objectplater.py:35 +msgid "Plate building tool" +msgstr "Outil de composition de plateau" + +#: printrun/objectplater.py:45 +msgid "Clear" +msgstr "Vider" + +#: printrun/objectplater.py:49 +msgid "Load" +msgstr "Charger" + +#: printrun/objectplater.py:53 +msgid "Snap to Z = 0" +msgstr "Placer en Z = 0" + +#: printrun/objectplater.py:57 +msgid "Put at center" +msgstr "Placer au centre" + +#: printrun/objectplater.py:61 +msgid "Delete" +msgstr "Supprimer" + +#: printrun/objectplater.py:65 +msgid "Auto arrange" +msgstr "Arrangement auto" + +#: printrun/objectplater.py:69 +msgid "Export" +msgstr "Exporter" + +#: printrun/objectplater.py:73 +msgid "Done" +msgstr "Terminé" + +#: printrun/objectplater.py:151 +msgid "Autoplating" +msgstr "Placement auto" + +#: printrun/objectplater.py:199 +msgid "Bed full, sorry sir :(" +msgstr "Plateau plein, désolé :(" + +#: printrun/objectplater.py:209 +msgid "" +"Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "" +"Êtes vous sur de vouloir vider le plateau ? Tous les changement non " +"sauvegardés seront perdus." + +#: printrun/objectplater.py:210 +msgid "Clear the grid?" +msgstr "Vider la grille ?" + +#: printrun/objectplater.py:256 +msgid "Pick file to load" +msgstr "Veuillez choisir le fichier à charger" + +#: printrun/objectplater.py:267 +msgid "Pick file to save to" +msgstr "Veuillez choisir le fichier dans lequel enregistrer" + +#: printrun/plater.py:212 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL|OpenSCAD files (*.scad)|*.scad" +msgstr "" +"Fichiers STL (*.stl;*.STL)|*.stl;*.STL|Fichiers OpenSCAD (*.scad)|*.scad" + +#: printrun/plater.py:213 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL" +msgstr "Fichiers STL (*.stl;*.STL)|*.stl;*.STL" + +#: printrun/plater.py:244 +msgid "Loading STL file failed" +msgstr "Échec de chargement du fichier STL" + +#: printrun/plater.py:244 printrun/plater.py:251 +msgid "Error" +msgstr "Erreur" + +#: printrun/plater.py:251 +msgid "Loading OpenSCAD file failed" +msgstr "Échec de chargement du fichier OpenSCAD" + +#: printrun/plater.py:284 +msgid "Couldn't load non-existing file %s" +msgstr "Impossible de charger le fichier inexistant %s" + +#: printrun/plater.py:360 +msgid "Wrote plate to %s" +msgstr "Plateau écrit dans %s" + +#: printrun/plater.py:367 +msgid "" +"Failed to use simarrange for plating, falling back to the standard method" +msgstr "" +"Impossible d'utiliser simarrange pour faire l'arrangement, utilisation de la " +"méthode standard" + +#: printrun/plater.py:372 +msgid "Autoplating using simarrange" +msgstr "Arrangement automatique en utilisant simarrange" + +#: printrun/plater.py:387 +msgid "Plate full, please remove some objects" +msgstr "Plateau plein, veuillez retirer des objets" + +#: printrun/plater.py:404 +msgid "simarrange failed" +msgstr "Échec de simarrange" + +#: printrun/printcore.py:177 +msgid "Could not connect to %s:%s:" +msgstr "Impossible de se connecter à %s:%s:" + +#: printrun/printcore.py:178 +msgid "Socket error %s:" +msgstr "Erreur réseau %s:" + +#: printrun/printcore.py:191 printrun/printcore.py:196 +msgid "Could not connect to %s at baudrate %s:" +msgstr "Impossible de se connecter à %s à la vitesse %s:" + +#: printrun/printcore.py:192 +msgid "Serial error: %s" +msgstr "Erreur série: %s" + +#: printrun/printcore.py:197 +#, fuzzy +msgid "IO error: %s" +msgstr "Erreur série: %s" + +#: printrun/printcore.py:231 +msgid "Can't read from printer (disconnected?) (SelectError {0}): {1}" +msgstr "" +"Impossible de lire depuis l'imprimante (déconnectée ?) (SelectError {0}): {1}" + +#: printrun/printcore.py:234 +msgid "SelectError ({0}): {1}" +msgstr "SelectError ({0}): {1}" + +#: printrun/printcore.py:237 +msgid "Can't read from printer (disconnected?) (SerialException): {0}" +msgstr "" +"Impossible de lire depuis l'imprimante (déconnectée ?) (SerialException): {0}" + +#: printrun/printcore.py:240 +msgid "Can't read from printer (disconnected?) (Socket error {0}): {1}" +msgstr "" +"Impossible de lire depuis l'imprimante (déconnectée ?) (Socket error {0}): " +"{1}" + +#: printrun/printcore.py:245 +msgid "Can't read from printer (disconnected?) (OS Error {0}): {1}" +msgstr "" +"Impossible de lire depuis l'imprimante (déconnectée ?) (OS Error {0}): {1}" + +#: printrun/printcore.py:259 +msgid "Aborting connection attempt after 4 failed writes." +msgstr "Annulation de la tentative de connexion suite à 4 envois échoués." + +#: printrun/printcore.py:462 printrun/printcore.py:470 +#: printrun/pronsole.py:1192 printrun/pronsole.py:1250 +#: printrun/pronterface.py:209 printrun/pronterface.py:962 +#: printrun/pronterface.py:1111 printrun/pronterface.py:1178 +msgid "Not connected to printer." +msgstr "Imprimante non connectée." + +#: printrun/printcore.py:479 +msgid "Print start callback failed with:" +msgstr "La fonction de rappel de début d'impression a échoué:" + +#: printrun/printcore.py:490 +msgid "Print end callback failed with:" +msgstr "La fonction de rappel de fin d'impression a échoué:" + +#: printrun/printcore.py:493 +msgid "Print thread died due to the following error:" +msgstr "Le fil d'impression a échoué à cause de l'erreur suivante:" + +#: printrun/printcore.py:584 +#, fuzzy +msgid "Could not analyze command %s:" +msgstr "Attention: impossible d'analyzer la commande %s:" + +#: printrun/printcore.py:601 +#, fuzzy +msgid "Can't write to printer (disconnected ?):" +msgstr "" +"Impossible d'écrire à l'imprimante (déconnectée ?) (SerialException): {0}" + +#: printrun/printcore.py:604 +msgid "Can't write to printer (disconnected?) (Socket error {0}): {1}" +msgstr "" +"Impossible d'écrire à l'imprimante (déconnectée ?) (Socket error {0}): {1}" + +#: printrun/printcore.py:607 +msgid "Can't write to printer (disconnected?) (SerialException): {0}" +msgstr "" +"Impossible d'écrire à l'imprimante (déconnectée ?) (SerialException): {0}" + +#: printrun/printcore.py:610 +msgid "Socket connection broken, disconnected. ({0}): {1}" +msgstr "La connection par socket a cassé, déconnexion. ({0}): {1}" + +#: printrun/pronsole.py:72 +msgid "Default: " +msgstr "Défaut: " + +#: printrun/pronsole.py:73 +msgid "(Control-doubleclick to reset to default value)" +msgstr "" +"(Effectuer un Control+double click pour revenir à la valeur par défaut)" + +#: printrun/pronsole.py:108 +msgid "" +"Are you sure you want to reset the setting to the default value: {0!r} ?" +msgstr "" +"Etes-vous sûr de vouloir réinitialiser ce paramètre à sa valeur par défaut : " +"{0!r} ?" + +#: printrun/pronsole.py:108 +msgid "Confirm set default" +msgstr "Confirmation de retour au défaut" + +#: printrun/pronsole.py:258 +msgid "Width" +msgstr "Largeur" + +#: printrun/pronsole.py:261 +msgid "Depth" +msgstr "Longueur" + +#: printrun/pronsole.py:264 +msgid "Height" +msgstr "Hauteur" + +#: printrun/pronsole.py:267 +msgid "X offset" +msgstr "Offset en X" + +#: printrun/pronsole.py:270 +msgid "Y offset" +msgstr "Offset en Y" + +#: printrun/pronsole.py:273 +msgid "Z offset" +msgstr "Offset en Z" + +#: printrun/pronsole.py:276 +msgid "X home pos." +msgstr "Position du homing en X" + +#: printrun/pronsole.py:279 +msgid "Y home pos." +msgstr "Position du homing en Y" + +#: printrun/pronsole.py:282 +msgid "Z home pos." +msgstr "Position du homing en Z" + +#: printrun/pronsole.py:301 +msgid "Port used to communicate with printer" +msgstr "Port utilisé pour communiquer avec l'imprimante" + +#: printrun/pronsole.py:301 +msgid "Serial port" +msgstr "Port série" + +#: printrun/pronsole.py:302 +msgid "Baud rate" +msgstr "Débit de la communication" + +#: printrun/pronsole.py:302 +msgid "Communications Speed" +msgstr "Vitesse de communication" + +#: printrun/pronsole.py:303 +msgid "TCP streaming mode" +msgstr "" + +#: printrun/pronsole.py:303 +msgid "" +"When using a TCP connection to the printer, the streaming mode will not wait " +"for acks from the printer to send new commands. This will break things such " +"as ETA prediction, but can result in smoother prints." +msgstr "" + +#: printrun/pronsole.py:304 +msgid "Bed temperature for ABS" +msgstr "Température du lit pour l'ABS" + +#: printrun/pronsole.py:304 +msgid "Heated Build Platform temp for ABS (deg C)" +msgstr "Température du plateau pour l'ABS (deg C)" + +#: printrun/pronsole.py:305 +msgid "Bed temperature for PLA" +msgstr "Température du lit pour le PLA" + +#: printrun/pronsole.py:305 +msgid "Heated Build Platform temp for PLA (deg C)" +msgstr "Température du plateau pour le PLA (deg C)" + +#: printrun/pronsole.py:306 +msgid "Extruder temp for ABS (deg C)" +msgstr "Température de l'extrudeuse pour l'ABS (deg C)" + +#: printrun/pronsole.py:306 +msgid "Extruder temperature for ABS" +msgstr "Température de l'extrudeuse pour l'ABS" + +#: printrun/pronsole.py:307 +msgid "Extruder temp for PLA (deg C)" +msgstr "Température de l'extrudeuse pour le PLA (deg C)" + +#: printrun/pronsole.py:307 +msgid "Extruder temperature for PLA" +msgstr "Température de l'extrudeuse pour le PLA" + +#: printrun/pronsole.py:308 +msgid "Feedrate for Control Panel Moves in X and Y (mm/min)" +msgstr "Vitesse pour les déplacements manuels en X et Y (mm/min)" + +#: printrun/pronsole.py:308 +msgid "X && Y manual feedrate" +msgstr "Vitesse manuelle X && Y" + +#: printrun/pronsole.py:309 +msgid "Feedrate for Control Panel Moves in Z (mm/min)" +msgstr "Vitesse pour les déplacements manuels en Z (mm/min)" + +#: printrun/pronsole.py:309 +msgid "Z manual feedrate" +msgstr "Vitesse manuelle Z" + +#: printrun/pronsole.py:310 +msgid "E manual feedrate" +msgstr "Vitesse manuelle E" + +#: printrun/pronsole.py:310 +msgid "Feedrate for Control Panel Moves in Extrusions (mm/min)" +msgstr "Vitesse pour les déplacements manuels en E (mm/min)" + +#: printrun/pronsole.py:311 +msgid "Slice command" +msgstr "Commande de slicing" + +#: printrun/pronsole.py:312 +msgid "Slice settings command" +msgstr "Commande pour les paramètres du slicer" + +#: printrun/pronsole.py:312 +msgid "Slicer options command" +msgstr "Commande pour " + +#: printrun/pronsole.py:313 +msgid "Executable to run when the print is finished" +msgstr "Exécutable à exécuter quand l'impression est terminée" + +#: printrun/pronsole.py:313 +msgid "Final command" +msgstr "Script post-impression" + +#: printrun/pronsole.py:314 +#, fuzzy +msgid "Error command" +msgstr "Commandes externes" + +#: printrun/pronsole.py:314 +#, fuzzy +msgid "Executable to run when an error occurs" +msgstr "Exécutable à exécuter quand l'impression est terminée" + +#: printrun/pronsole.py:381 +msgid "Failed to run callback after setting \"%s\":" +msgstr "" + +#: printrun/pronsole.py:469 +msgid "Build dimensions" +msgstr "Dimensions utiles" + +#: printrun/pronsole.py:469 +msgid "" +"Dimensions of Build Platform\n" +" & optional offset of origin\n" +" & optional switch position\n" +"\n" +"Examples:\n" +" XXXxYYY\n" +" XXX,YYY,ZZZ\n" +" XXXxYYYxZZZ+OffX+OffY+OffZ\n" +"XXXxYYYxZZZ+OffX+OffY+OffZ+HomeX+HomeY+HomeZ" +msgstr "" +"Dimensions de la plateforme d'impression\n" +" & offset de l'origine\n" +" & position des endstops\n" +"\n" +"Exemples:\n" +" XXXxYYY\n" +" XXX,YYY,ZZZ\n" +" XXXxYYYxZZZ+OffX+OffY+OffZ\n" +"XXXxYYYxZZZ+OffX+OffY+OffZ+HomeX+HomeY+HomeZ" + +#: printrun/pronsole.py:495 +msgid "" +"Welcome to the printer console! Type \"help\" for a list of available " +"commands." +msgstr "" + +#: printrun/pronsole.py:657 printrun/pronsole.py:665 printrun/pronsole.py:673 +#: printrun/pronsole.py:1519 printrun/pronsole.py:1546 +#: printrun/pronsole.py:1618 printrun/pronterface.py:357 +#: printrun/pronterface.py:377 printrun/pronterface.py:394 +msgid "Printer is not online." +msgstr "Imprimante déconnectée." + +#: printrun/pronsole.py:692 +msgid "Exiting program. Goodbye!" +msgstr "Fin du programme. Au revoir !" + +#: printrun/pronsole.py:697 +msgid "Disconnects from the printer and exits the program." +msgstr "Déconnecte l'imprimante et quitte le programme." + +#: printrun/pronsole.py:974 +msgid "" +"load this file on startup instead of .pronsolerc ; you may chain config " +"files, if so settings auto-save will use the last specified file" +msgstr "" +"charger ce fichier à la place de .pronsolerc ; vous pouvez charger plusieurs " +"fichiers de configuration, dans ce cas les paramètres seront sauvegardés " +"dans le dernier fichier spécifié" + +#: printrun/pronsole.py:975 +msgid "" +"executes command after configuration/.pronsolerc is loaded ; macros/settings " +"from these commands are not autosaved" +msgstr "" +"exécute une commande après le chargement de la configuration/.pronsolerc ; " +"les macros et paramètres de ces commandes ne seront pas sauvegardées " +"automatiquement" + +#: printrun/pronsole.py:976 +msgid "file to load" +msgstr "fichier à charger" + +#: printrun/pronsole.py:1104 +#, fuzzy +msgid "Loaded %s, %d lines." +msgstr "%s chargé, %d lignes" + +#: printrun/pronsole.py:1105 printrun/pronterface.py:1381 +#, fuzzy +msgid "Estimated duration: %d layers, %s" +msgstr "Durée estimée : %s" + +#: printrun/pronsole.py:1134 +msgid "No file name given." +msgstr "Aucun nom de fichier fourni." + +#: printrun/pronsole.py:1140 +msgid "Skeining file: %s" +msgstr "Découpe du fichier: %s" + +#: printrun/pronsole.py:1142 printrun/pronterface.py:1305 +msgid "File not found!" +msgstr "Fichier non trouvé" + +#: printrun/pronsole.py:1147 +msgid "Entering slicer settings: %s" +msgstr "Accès au paramètres de découpée: %s" + +#: printrun/pronsole.py:1151 +msgid "Slicing: " +msgstr "Slicing :" + +#: printrun/pronsole.py:1158 +msgid "Loading sliced file." +msgstr "Chargement du fichier slicé." + +#: printrun/pronsole.py:1161 +msgid "Slicing failed: %s" +msgstr "Échec de la découpe: %s" + +#: printrun/pronsole.py:1174 +msgid "" +"Creates a gcode file from an stl model using the slicer (with tab-completion)" +msgstr "" +"Crée un fichier gcode à partir d'un modèle stl avec la découpeuse (avec tab-" +"complétion)" + +#: printrun/pronsole.py:1175 +#, fuzzy +msgid "slice filename.stl - create gcode file" +msgstr "skein fichier.stl - découper fichier.stl en un gcode" + +#: printrun/pronsole.py:1176 +#, fuzzy +msgid "" +"slice filename.stl view - create gcode file and view using skeiniso (if " +"using skeinforge)" +msgstr "" +"skein fichier.stl view - découper fichier.stl en un gcode et le visualiser " +"avec skeiniso" + +#: printrun/pronsole.py:1177 +#, fuzzy +msgid "slice set - adjust slicer settings" +msgstr "skein set - régler les paramètres de découpe" + +#: printrun/pronsole.py:1189 +msgid "Please enter target name in 8.3 format." +msgstr "Veuillez entrer un nom au format 8.3." + +#: printrun/pronsole.py:1195 +msgid "Uploading as %s" +msgstr "Envoi en tant que %s" + +#: printrun/pronsole.py:1196 +msgid "Uploading %s" +msgstr "Envoi de %s" + +#: printrun/pronsole.py:1198 +msgid "Press Ctrl-C to interrupt upload." +msgstr "Appuyer sur Ctrl-C pour interrompre l'envoi." + +#: printrun/pronsole.py:1201 +msgid "Progress: " +msgstr "Progrès :" + +#: printrun/pronsole.py:1212 +msgid "Upload completed. %s should now be on the card." +msgstr "Envoi terminé. %s devrait maintenant se trouver sur la carte." + +#: printrun/pronsole.py:1216 +msgid "...interrupted!" +msgstr "...interrompu !" + +#: printrun/pronsole.py:1218 +msgid "Something wrong happened while uploading:" +msgstr "" + +#: printrun/pronsole.py:1224 +msgid "A partial file named %s may have been written to the sd card." +msgstr "Un fichier incomplet nommé %s peut avoir été écrit sur la carte SD." + +#: printrun/pronsole.py:1241 +msgid "" +"Send a loaded gcode file to the printer. Load a file with the load command " +"first." +msgstr "" +"Envoyer un fichier gcode chargé à l'imprimante. Veuillez d'abord charger un " +"fichier avec la commande \"load\"." + +#: printrun/pronsole.py:1243 +msgid "Send a loaded gcode file to the printer. You have %s loaded right now." +msgstr "" +"Envoyer un fichier gcode chargé à l'imprimante. %s est chargé pour l'instant." + +#: printrun/pronsole.py:1247 printrun/pronterface.py:730 +#: printrun/pronterface.py:1108 +msgid "No file loaded. Please use load first." +msgstr "Aucun fichier chargé. Veuillez charger un fichier avant." + +#: printrun/pronsole.py:1252 +msgid "Printing %s" +msgstr "Impression de %s" + +#: printrun/pronsole.py:1253 +msgid "You can monitor the print with the monitor command." +msgstr "Vous pouvez suivre l'impression avec la commande \"monitor\"." + +#: printrun/pronsole.py:1261 +msgid "Not printing, cannot pause." +msgstr "Pas d'impression en cours, impossible de mettre en pause." + +#: printrun/pronsole.py:1267 +msgid "Pauses a running print" +msgstr "Met en pause l'impression en cours" + +#: printrun/pronsole.py:1274 +msgid "Not paused, unable to resume. Start a print first." +msgstr "" +"Pas d'impression en pause, impossible de continuer. Veuillez d'abord lancer " +"une impression." + +#: printrun/pronsole.py:1284 +msgid "Resumes a paused print." +msgstr "Continue une impression en pause." + +#: printrun/pronsole.py:1293 +msgid "Files on SD card:" +msgstr "Fichiers sur la carte SD :" + +#: printrun/pronsole.py:1307 printrun/pronsole.py:1349 +#: printrun/pronsole.py:1562 +msgid "Printer is not online. Please connect to it first." +msgstr "Imprimante déconnectée. Veuillez vous y connecter." + +#: printrun/pronsole.py:1312 +msgid "Lists files on the SD card" +msgstr "Liste les fichiers sur la carte SD" + +#: printrun/pronsole.py:1316 printrun/pronterface.py:1645 +msgid "Opening file failed." +msgstr "L'ouverture du fichier a échoué" + +#: printrun/pronsole.py:1322 printrun/pronterface.py:1651 +msgid "Starting print" +msgstr "Début de l'impression..." + +#: printrun/pronsole.py:1345 +msgid "Resets the printer." +msgstr "Remet à zéro l'imprimante." + +#: printrun/pronsole.py:1355 +msgid "File is not present on card. Please upload it first." +msgstr "Le fichier n'est pas sur la carte, veuillez d'abord l'y envoyer." + +#: printrun/pronsole.py:1359 +msgid "Printing file: %s from SD card." +msgstr "Impression du fichier %s depuis la carte SD" + +#: printrun/pronsole.py:1360 +msgid "Requesting SD print..." +msgstr "Demande de l'impression depuis la carte SD..." + +#: printrun/pronsole.py:1364 +msgid "Print a file from the SD card. Tab completes with available file names." +msgstr "" +"Imprimer un fichier depuis la carte SD. La touche Tab complétera les " +"fichiers disponibles." + +#: printrun/pronsole.py:1365 +msgid "sdprint filename.g" +msgstr "sdprint fichier.g" + +#: printrun/pronsole.py:1382 +msgid "Print resumed at: %s" +msgstr "Impression relancée à : %s" + +#: printrun/pronsole.py:1384 +msgid "Print started at: %s" +msgstr "Impression lancée à : %s" + +#: printrun/pronsole.py:1392 +msgid "Failed to inhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1399 +msgid "Failed to uninhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1403 +msgid "Print ended at: %(end_time)s and took %(duration)s" +msgstr "Impression terminée à : %(end_time)s après avoir duré %(duration)s" + +#: printrun/pronsole.py:1457 +msgid "Printer is not currently printing. No ETA available." +msgstr "" + +#: printrun/pronsole.py:1460 +#, fuzzy +msgid "Est: %s of %s remaining" +msgstr " ETA: %s restant sur %s | " + +#: printrun/pronsole.py:1465 +msgid "Displays estimated remaining print time." +msgstr "" + +#: printrun/pronsole.py:1498 +msgid "Read the extruder and bed temperature." +msgstr "Lire la température de l'extrudeuse et du plateau." + +#: printrun/pronsole.py:1507 printrun/pronsole.py:1540 +msgid "You must enter a temperature." +msgstr "Vous devez saisir une température." + +#: printrun/pronsole.py:1512 +msgid "" +"%s is a high temperature to set your extruder to. Are you sure you want to " +"do that?" +msgstr "" +"%s est une température élevée pour une extrudeuse. Êtes-vous sûr de vouloir " +"faire ça ?" + +#: printrun/pronsole.py:1517 +msgid "Setting hotend temperature to %s degrees Celsius." +msgstr "Réglage de la température de la buse à %s degrés Celsius." + +#: printrun/pronsole.py:1521 printrun/pronterface.py:359 +msgid "" +"You cannot set negative temperatures. To turn the hotend off entirely, set " +"its temperature to 0." +msgstr "" +"Vous ne pouvez pas régler une température négative. Pour éteindre la buse, " +"réglez sa température à 0°C." + +#: printrun/pronsole.py:1524 +msgid "Sets the hotend temperature to the value entered." +msgstr "Règle de la température de la buse à la valeur saisie." + +#: printrun/pronsole.py:1525 printrun/pronsole.py:1552 +msgid "Enter either a temperature in celsius or one of the following keywords" +msgstr "" +"Entrez soit une température en degrés celsius soit un des mots-clés suivants" + +#: printrun/pronsole.py:1544 +msgid "Setting bed temperature to %s degrees Celsius." +msgstr "Réglage de la température du plateau à %s degrés Celsius." + +#: printrun/pronsole.py:1548 printrun/pronterface.py:379 +msgid "" +"You cannot set negative temperatures. To turn the bed off entirely, set its " +"temperature to 0." +msgstr "" +"Vous ne pouvez pas régler une température négative. Pour désactiver votre " +"plateau chauffant, réglez sa température à 0°C." + +#: printrun/pronsole.py:1551 +msgid "Sets the bed temperature to the value entered." +msgstr "Règle de la température du plateau à la valeur saisie." + +#: printrun/pronsole.py:1565 +msgid "Printer is not printing. Please print something before monitoring." +msgstr "" +"L'imprimante n'est pas en cours d'impression. Veuillez lancer une impression " +"avant d'activer le suivi." + +#: printrun/pronsole.py:1567 +msgid "Monitoring printer, use ^C to interrupt." +msgstr "Imprimante sous surveillance, utiliser ^C pour interrompre." + +#: printrun/pronsole.py:1572 printrun/pronterface.py:329 +msgid "Invalid period given." +msgstr "La période donnée est invalide" + +#: printrun/pronsole.py:1573 +msgid "Updating values every %f seconds." +msgstr "Mise à jour des valeurs toutes les %f secondes." + +#: printrun/pronsole.py:1584 printrun/pronsole.py:1587 +msgid "Print progress: " +msgstr "Progrès de l'impression : " + +#: printrun/pronsole.py:1595 printrun/pronterface.py:333 +msgid "Done monitoring." +msgstr "Surveillance de l'imprimante effectuée." + +#: printrun/pronsole.py:1599 +msgid "Monitor a machine's temperatures and an SD print's status." +msgstr "Monitorer les températures de la machine et l'état de l'impression SD" + +#: printrun/pronsole.py:1600 +msgid "" +"monitor - Reports temperature and SD print status (if SD printing) every 5 " +"seconds" +msgstr "" +"monitor - Rapporte les températures et l'état d'impression SD (si " +"applicable) toutes les 5 secondes" + +#: printrun/pronsole.py:1601 +msgid "" +"monitor 2 - Reports temperature and SD print status (if SD printing) every 2 " +"seconds" +msgstr "" +"monitor 2 - Rapporte les températures et l'état d'impression SD (si " +"applicable) toutes les 2 secondes" + +#: printrun/pronsole.py:1612 +msgid "You must specify the tool index as an integer." +msgstr "Vous devez spécifier l'indice de l'outil avec un entier." + +#: printrun/pronsole.py:1616 +msgid "Using tool %d." +msgstr "Utilisation de l'outil %d." + +#: printrun/pronsole.py:1620 +msgid "You cannot set negative tool numbers." +msgstr "Vous ne pouvez pas choisir des indices d'outil négatifs." + +#: printrun/pronsole.py:1623 +msgid "" +"Switches to the specified tool (e.g. doing tool 1 will emit a T1 G-Code)." +msgstr "" +"Passer à l'outil spécifié (e.g. faire \"tool 1\" enverra un G-Code T1)." + +#: printrun/pronsole.py:1627 +msgid "No move specified." +msgstr "Aucun déplacement spécifié." + +#: printrun/pronsole.py:1630 printrun/pronsole.py:1692 +#: printrun/pronsole.py:1735 printrun/pronsole.py:1762 +msgid "" +"Printer is currently printing. Please pause the print before you issue " +"manual commands." +msgstr "" +"L'imprimante est actuellement en train d'imprimer. Veuillez mettre " +"l'impression en pause avant d'envoyer des commandes manuelles." + +#: printrun/pronsole.py:1633 printrun/pronsole.py:1759 +msgid "Printer is not online. Unable to move." +msgstr "Imprimante déconnectée. Impossible de bouger." + +#: printrun/pronsole.py:1649 +msgid "Unknown axis." +msgstr "Axe inconnu." + +#: printrun/pronsole.py:1654 +msgid "Invalid distance" +msgstr "Distance invalide" + +#: printrun/pronsole.py:1665 +msgid "Move an axis. Specify the name of the axis and the amount. " +msgstr "Déplacer un axe. Spécifier le nom de l'axe et la distance." + +#: printrun/pronsole.py:1666 +msgid "" +"move X 10 will move the X axis forward by 10mm at %s mm/min (default XY " +"speed)" +msgstr "" +"move X 10 va déplacer l'axe X en avant de 10mm à %s mm/min (vitesse XY par " +"défaut)" + +#: printrun/pronsole.py:1667 +msgid "move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min" +msgstr "move Y 10 5000 va déplacer l'axe Y en avant de 10mm à 5000mm/min" + +#: printrun/pronsole.py:1668 +msgid "" +"move Z -1 will move the Z axis down by 1mm at %s mm/min (default Z speed)" +msgstr "" +"move Z -1 va déplacer l'axe Z vers le bas de 1mm à %s mm/min (vitesse Z par " +"défaut)" + +#: printrun/pronsole.py:1669 +msgid "Common amounts are in the tabcomplete list." +msgstr "Des longueurs standard sont dans la liste de tabcomplétion." + +#: printrun/pronsole.py:1699 printrun/pronsole.py:1742 +msgid "Invalid length given." +msgstr "La longueur donnée est invalide." + +#: printrun/pronsole.py:1704 printrun/pronsole.py:1747 +msgid "Invalid speed given." +msgstr "La vitesse donnée est invalide." + +#: printrun/pronsole.py:1712 +msgid "Extruding %fmm of filament." +msgstr "Extrusion de %fmm de filament." + +#: printrun/pronsole.py:1714 +msgid "Reversing %fmm of filament." +msgstr "Rétractation de %fmm de filament." + +#: printrun/pronsole.py:1716 +msgid "Length is 0, not doing anything." +msgstr "Longueur nulle, rien à faire." + +#: printrun/pronsole.py:1722 +msgid "" +"Extrudes a length of filament, 5mm by default, or the number of mm given as " +"a parameter" +msgstr "" +"Extruder une longueur de filament, 5mm par défaut, ou le nombre de mm " +"spécifiés en paramètre" + +#: printrun/pronsole.py:1723 +msgid "extrude - extrudes 5mm of filament at 300mm/min (5mm/s)" +msgstr "extrude - extruder 5mm de filament à 300mm/min (5mm/s)" + +#: printrun/pronsole.py:1724 +msgid "extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)" +msgstr "extrude 20 - extruder 20mm de filament à 300mm/min (5mm/s)" + +#: printrun/pronsole.py:1725 +msgid "extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)" +msgstr "extrude -5 - RETRACTER 5mm de filament à 300mm/min (5mm/s)" + +#: printrun/pronsole.py:1726 +msgid "extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "extrude 10 210 - extruder 10mm de filament à 210mm/min (3.5mm/s)" + +#: printrun/pronsole.py:1732 +msgid "Printer is not online. Unable to reverse." +msgstr "Imprimante déconnectée. Impossible de retirer." + +#: printrun/pronsole.py:1751 +msgid "" +"Reverses the extruder, 5mm by default, or the number of mm given as a " +"parameter" +msgstr "" +"Rétracte l'extrudeuse, de 5mm par défaut, ou du nombre de mm donnés en " +"paramètre" + +#: printrun/pronsole.py:1752 +msgid "reverse - reverses 5mm of filament at 300mm/min (5mm/s)" +msgstr "reverse - rétracte 5mm de filament à 300mm/min (5mm/s)" + +#: printrun/pronsole.py:1753 +msgid "reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)" +msgstr "reverse 20 - rétracte 20mm de filament à 300mm/min (5mm/s)" + +#: printrun/pronsole.py:1754 +msgid "reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "reverse 10 210 - rétracte 10mm de filament à 210mm/min (3.5mm/s)" + +#: printrun/pronsole.py:1755 +msgid "reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)" +msgstr "reverse -5 - EXTRUDE 5mm de filament à 300mm/min (5mm/s)" + +#: printrun/pronsole.py:1777 +msgid "Homes the printer" +msgstr "Ramène l'imprimante à zéro" + +#: printrun/pronsole.py:1778 +msgid "home - homes all axes and zeroes the extruder(Using G28 and G92)" +msgstr "" +"home - ramène tous les axes à l'origine et remet l'extrudeuse à zéro (avec " +"un G28 et un G92)" + +#: printrun/pronsole.py:1779 +msgid "home xy - homes x and y axes (Using G28)" +msgstr "home xy - ramène les axes x et y à l'origine (avec un G28)" + +#: printrun/pronsole.py:1780 +msgid "home z - homes z axis only (Using G28)" +msgstr "home z - ramène l'axe z à l'origine (avec un G28)" + +#: printrun/pronsole.py:1781 +msgid "home e - set extruder position to zero (Using G92)" +msgstr "home e - réinitialise l'extrudeuse à la position zéro (avec un G92)" + +#: printrun/pronsole.py:1782 +msgid "home xyze - homes all axes and zeroes the extruder (Using G28 and G92)" +msgstr "" +"home xyze - ramène tous les axes à l'origine et réinitialise l'extrudeuse " +"(avec un G28 et un G92)" + +#: printrun/pronsole.py:1790 +msgid "; Motors off" +msgstr "; Arrêt moteur" + +#: printrun/pronsole.py:1792 +msgid "; Extruder off" +msgstr "; Arrêt extrudeuse" + +#: printrun/pronsole.py:1794 +msgid "; Heatbed off" +msgstr "; Arrêt plateau chauffant" + +#: printrun/pronsole.py:1796 +msgid "; Fan off" +msgstr "; Arrêt ventilateur" + +#: printrun/pronsole.py:1798 +msgid "; Power supply off" +msgstr "; Arrêt alimentation" + +#: printrun/pronsole.py:1801 +msgid "Printer is not online. Unable to turn it off." +msgstr "Imprimante déconnectée. Impossible de l'éteindre." + +#: printrun/pronsole.py:1804 +msgid "Turns off everything on the printer" +msgstr "Éteint tout sur l'imprimante" + +#: printrun/pronsole.py:1815 +msgid "G-Code calling host command \"%s\"" +msgstr "" + +#: printrun/pronsole.py:1824 +msgid "" +"Runs a custom script. Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronsole.py:1832 +msgid "" +"Runs a custom script which output gcode which will in turn be executed. " +"Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronterface.py:149 +msgid "Motors off" +msgstr "Arrêter les moteurs" + +#: printrun/pronterface.py:149 +msgid "Switch all motors off" +msgstr "Arrêter tous les moteurs" + +#: printrun/pronterface.py:150 +msgid "Advance extruder by set length" +msgstr "Extruder sur la longueur donnée" + +#: printrun/pronterface.py:150 +msgid "Extrude" +msgstr "Extruder" + +#: printrun/pronterface.py:151 +msgid "Reverse" +msgstr "Inverser" + +#: printrun/pronterface.py:151 +msgid "Reverse extruder by set length" +msgstr "Inverser l'extrudeur sur la longueur donnée" + +#: printrun/pronterface.py:165 +msgid "Pronterface" +msgstr "Pronterface" + +#: printrun/pronterface.py:192 +msgid "" +"# I moved all your custom buttons into .pronsolerc.\n" +"# Please don't add them here any more.\n" +"# Backup of your old buttons is in custombtn.old\n" +msgstr "" +"# Tous vos boutons personalisés ont été déplacés dans le fichier ." +"pronsolerc.\n" +"# Veuillez ne plus en ajouter ici.\n" +"# Une sauvegarde de vos anciens boutons est dans le fichier custombtn.old\n" + +#: printrun/pronterface.py:197 +msgid "" +"Note!!! You have specified custom buttons in both custombtn.txt and ." +"pronsolerc" +msgstr "" +"Remarque! Vous avez spécifié des boutons personnalisés dans custombtn.txt et " +"aussi dans .pronsolerc" + +#: printrun/pronterface.py:198 +msgid "" +"Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt" +msgstr "" +"custombtn.txt ignoré. Retirez tous les boutons en cours pour revenir à " +"custombtn.txt" + +#: printrun/pronterface.py:331 +msgid "Monitoring printer." +msgstr "Imprimante sous surveillance." + +#: printrun/pronterface.py:354 +msgid "Setting hotend temperature to %f degrees Celsius." +msgstr "Réglage de la température de la buse à %f degrés Celsius." + +#: printrun/pronterface.py:361 printrun/pronterface.py:381 +msgid "You must enter a temperature. (%s)" +msgstr "Vous devez saisir une température. (%s)" + +#: printrun/pronterface.py:374 +msgid "Setting bed temperature to %f degrees Celsius." +msgstr "Réglage de la température du plateau à %f degrés Celsius." + +#: printrun/pronterface.py:392 +msgid "Setting print speed factor to %d%%." +msgstr "" + +#: printrun/pronterface.py:396 +#, fuzzy +msgid "You must enter a speed. (%s)" +msgstr "Vous devez saisir une température. (%s)" + +#: printrun/pronterface.py:470 +msgid "Plate function activated" +msgstr "Fonction de composition activée" + +#: printrun/pronterface.py:479 +#, fuzzy +msgid "G-Code plate function activated" +msgstr "Fonction de composition activée" + +#: printrun/pronterface.py:486 +msgid "Plated %s" +msgstr "Plateau créé %s" + +#: printrun/pronterface.py:500 +msgid "SD Upload" +msgstr "Copier sur SD" + +#: printrun/pronterface.py:504 +msgid "SD Print" +msgstr "Imprimer depuis SD" + +#: printrun/pronterface.py:565 +msgid "" +"Manual move outside of the build volume prevented (see the \"Clamp manual " +"moves\" option)." +msgstr "" +"Déplacement en dehors du volume d'impression inhibé (voir l'option \"Limiter " +"les mouvements manuels\")." + +#: printrun/pronterface.py:621 +msgid "" +"Attempted to write invalid text to console, which could be due to an invalid " +"baudrate" +msgstr "" +"Impossible de transcrire un message dans la console, possiblement à cause " +"d'un mauvais réglage du baudrate." + +#: printrun/pronterface.py:655 +msgid " Opens file" +msgstr " Ouvrir un fichier" + +#: printrun/pronterface.py:655 +msgid "&Open..." +msgstr "&Ouvrir..." + +#: printrun/pronterface.py:663 +msgid " Clear output console" +msgstr " Effacer le contenu de la console de sortie" + +#: printrun/pronterface.py:663 +msgid "Clear console" +msgstr "Effacer la console" + +#: printrun/pronterface.py:664 +msgid " Closes the Window" +msgstr " Quitter le programme" + +#: printrun/pronterface.py:664 +msgid "E&xit" +msgstr "&Quitter" + +#: printrun/pronterface.py:665 +msgid "&File" +msgstr "&Fichier" + +#: printrun/pronterface.py:668 +msgid " Edit open file" +msgstr " Éditer le fichier ouvert" + +#: printrun/pronterface.py:668 +msgid "&Edit..." +msgstr "&Éditer..." + +#: printrun/pronterface.py:669 +msgid " Compose 3D models into a single plate" +msgstr "Composer plusieurs modèles 3D en un seul plateau" + +#: printrun/pronterface.py:669 +msgid "Plater" +msgstr "Plater" + +#: printrun/pronterface.py:670 +#, fuzzy +msgid " Compose G-Codes into a single plate" +msgstr "Composer plusieurs modèles 3D en un seul plateau" + +#: printrun/pronterface.py:670 +#, fuzzy +msgid "G-Code Plater" +msgstr "Plater" + +#: printrun/pronterface.py:671 +msgid " Exclude parts of the bed from being printed" +msgstr "Exclure des pièces du plateau lors de l'impression" + +#: printrun/pronterface.py:671 +msgid "Excluder" +msgstr "Exclueur" + +#: printrun/pronterface.py:672 +msgid " Project slices" +msgstr " Projeter les couches" + +#: printrun/pronterface.py:672 +msgid "Projector" +msgstr "Projecteur" + +#: printrun/pronterface.py:673 +msgid "&Tools" +msgstr "&Outils" + +#: printrun/pronterface.py:676 +msgid "" +" Recover previous print after a disconnect (homes X, Y, restores Z and E " +"status)" +msgstr "" +" Récupérer l'impression précédente après une déconnexion (ramène X, Y à 0, " +"restaure l'état de Z et E)" + +#: printrun/pronterface.py:676 +msgid "Recover" +msgstr "Récupérer" + +#: printrun/pronterface.py:679 +msgid "&Advanced" +msgstr "&Avancé" + +#: printrun/pronterface.py:686 +msgid "Print &settings" +msgstr "Paramètres d'impre&ssion" + +#: printrun/pronterface.py:687 +msgid "&Filament" +msgstr "&Filament" + +#: printrun/pronterface.py:688 +msgid "&Printer" +msgstr "&Imprimante" + +#: printrun/pronterface.py:694 +msgid "&Slic3r" +msgstr "&Slic3r" + +#: printrun/pronterface.py:696 +#, fuzzy +msgid "Failed to load Slic3r configuration:" +msgstr "Activer l'intégration de Slic3r" + +#: printrun/pronterface.py:702 +msgid "&Macros" +msgstr "&Macros" + +#: printrun/pronterface.py:703 +msgid "<&New...>" +msgstr "<&Nouvelle...>" + +#: printrun/pronterface.py:704 +msgid " Options dialog" +msgstr " Fenêtre des options" + +#: printrun/pronterface.py:704 +msgid "&Options" +msgstr "&Options" + +#: printrun/pronterface.py:706 +msgid " Adjust slicing settings" +msgstr " Régler les paramètres de slicing" + +#: printrun/pronterface.py:706 +#, fuzzy +msgid "Slicing settings" +msgstr "Paramètres de slicing" + +#: printrun/pronterface.py:708 +#, fuzzy +msgid "Debug communications" +msgstr "Vitesse de communication" + +#: printrun/pronterface.py:709 +msgid "Print all G-code sent to and received from the printer." +msgstr "Afficher tous les G-code envoyés et reçus." + +#: printrun/pronterface.py:713 +msgid "&Settings" +msgstr "&Paramètres" + +#: printrun/pronterface.py:719 +msgid "&About Printrun" +msgstr "&A propos de Printrun" + +#: printrun/pronterface.py:719 +msgid "Show about dialog" +msgstr "Afficher la fenêtre A propos" + +#: printrun/pronterface.py:720 +msgid "&Help" +msgstr "" + +#: printrun/pronterface.py:746 +msgid "" +"Printrun is a pure Python 3D printing (and other types of CNC) host software." +msgstr "" +"Printrun est un logiciel hôte pour imprimante 3D (et autres CNC) écrit en " +"pur Python" + +#: printrun/pronterface.py:750 +#, fuzzy +msgid "%.02fmm of filament have been extruded during prints" +msgstr "mm de filament utilisés pour cette impression" + +#: printrun/pronterface.py:781 +#, fuzzy +msgid "Monitor printer status" +msgstr "Imprimante sous surveillance." + +#: printrun/pronterface.py:781 +msgid "" +"Regularly monitor printer temperatures (required to have functional " +"temperature graph or gauges)" +msgstr "" + +#: printrun/pronterface.py:782 +msgid "Path to the simarrange binary to use in the STL plater" +msgstr "Chemin vers le binaire de simarrange à utiliser dans le plater de STL" + +#: printrun/pronterface.py:782 +msgid "Simarrange command" +msgstr "Commande simarrange" + +#: printrun/pronterface.py:783 +msgid "Circular build platform" +msgstr "" + +#: printrun/pronterface.py:783 +msgid "Draw a circular (or oval) build platform instead of a rectangular one" +msgstr "" + +#: printrun/pronterface.py:784 +msgid "Extruders count" +msgstr "Nombre d'extrudeuses" + +#: printrun/pronterface.py:784 +msgid "Number of extruders" +msgstr "Nombre d'extrudeuses" + +#: printrun/pronterface.py:785 +msgid "Clamp manual moves" +msgstr "Limiter les mouvements manuels" + +#: printrun/pronterface.py:785 +msgid "Prevent manual moves from leaving the specified build dimensions" +msgstr "" +"Empêcher les mouvements manuels de quitter le volume d'impression défini" + +#: printrun/pronterface.py:786 +msgid "Interface mode" +msgstr "Mode de l'interface" + +#: printrun/pronterface.py:786 +msgid "" +"Standard interface is a one-page, three columns layout with controls/" +"visualization/log\n" +"Compact mode is a one-page, two columns layout with controls + log/" +"visualization\n" +"Tabbed mode is a two-pages mode, where the first page shows controls and the " +"second one shows visualization and log." +msgstr "" +"Le mode Standard affiche les contrôles, la visualisation et la console sur 3 " +"colonnes\n" +"Le mode Compact regroupe contrôles et console sur 1 colonne et affiche la " +"visualisation sur une seconde\n" +"Le mode Tabbed présente deux onglets, l'un contenant les contrôles, l'autre " +"la visualisation et la console." + +#: printrun/pronterface.py:787 +msgid "Controls mode" +msgstr "" + +#: printrun/pronterface.py:787 +msgid "" +"Standard controls include all controls needed for printer setup and " +"calibration, while Mini controls are limited to the ones needed for daily " +"printing" +msgstr "" + +#: printrun/pronterface.py:788 +msgid "Add a menu to select Slic3r profiles directly from Pronterface" +msgstr "" +"Ajouter un menu pour sélectionner les profils Slic3r directement depuis " +"Pronterface" + +#: printrun/pronterface.py:788 +msgid "Enable Slic3r integration" +msgstr "Activer l'intégration de Slic3r" + +#: printrun/pronterface.py:789 +msgid "Update Slic3r default presets" +msgstr "Mettre à jour les profils par défaut de Slic3r" + +#: printrun/pronterface.py:789 +msgid "" +"When selecting a profile in Slic3r integration menu, also save it as the " +"default Slic3r preset" +msgstr "" +"Lors de la sélection d'un profil dans le menu d'intégration de Slic3r, " +"définir ce profil en tant que défaut pour Slic3r également" + +#: printrun/pronterface.py:790 +msgid "Main visualization" +msgstr "Visualisation principale" + +#: printrun/pronterface.py:790 +msgid "Select visualization for main window." +msgstr "Sélectionnez la visualisation pour la fenêtre principale" + +#: printrun/pronterface.py:791 +msgid "Use 3D in GCode viewer window" +msgstr "Utiliser la 3D pour la fenêtre de visualisation" + +#: printrun/pronterface.py:791 +msgid "Use 3D mode instead of 2D layered mode in the visualization window" +msgstr "" +"Utiliser le mode 3D à la place du mode 2D par couche dans la fenêtre de " +"visualisation" + +#: printrun/pronterface.py:792 +msgid "Use a lighter 3D visualization" +msgstr "Utiliser une visualisation 3D plus légère" + +#: printrun/pronterface.py:792 +msgid "" +"Use a lighter visualization with simple lines instead of extruded paths for " +"3D viewer" +msgstr "" +"Utiliser une visualisation plus légère avec des lignes à la place de tubes " +"extrudés dans la vue 3D" + +#: printrun/pronterface.py:793 +msgid "Track current layer in main 3D view" +msgstr "" + +#: printrun/pronterface.py:793 +msgid "Track the currently printing layer in the main 3D visualization" +msgstr "" + +#: printrun/pronterface.py:794 +msgid "Display temperature graph" +msgstr "Afficher le graphe de températures" + +#: printrun/pronterface.py:794 +msgid "Display time-lapse temperature graph" +msgstr "Afficher un graphe temporel de la température" + +#: printrun/pronterface.py:795 +msgid "Display graphical gauges for temperatures visualization" +msgstr "Afficher des jauges de température" + +#: printrun/pronterface.py:795 +msgid "Display temperature gauges" +msgstr "Afficher les jauges de température" + +#: printrun/pronterface.py:796 +msgid "Display a checkbox that, when check, locks most of Pronterface" +msgstr "" +"Afficher une case à cocher pour verrouiller une grande partie de l'imprimante" + +#: printrun/pronterface.py:796 +msgid "Display interface lock checkbox" +msgstr "Afficher une case à cocher pour pouvoir verrouiller l'inteface" + +#: printrun/pronterface.py:797 +msgid "If lock checkbox is enabled, lock the interface when starting a print" +msgstr "" +"Si la case de verrouillage est activée, verrouiler l'interface au lancement " +"d'une impression" + +#: printrun/pronterface.py:797 +msgid "Lock interface upon print start" +msgstr "Verrouiller l'interface au lancement d'une impression" + +#: printrun/pronterface.py:804 +msgid "Preview extrusion width" +msgstr "Largeur de l'extrusion pour la visualisation" + +#: printrun/pronterface.py:804 +msgid "Width of Extrusion in Preview" +msgstr "Largeur de l'extrusion dans la prévisualisation" + +#: printrun/pronterface.py:805 +msgid "Fine Grid Spacing" +msgstr "Espacement fin de la grille" + +#: printrun/pronterface.py:805 +msgid "Fine grid spacing" +msgstr "Espacement fin de la grille" + +#: printrun/pronterface.py:806 +msgid "Coarse Grid Spacing" +msgstr "Espacement large de la grille" + +#: printrun/pronterface.py:806 +msgid "Coarse grid spacing" +msgstr "Espacement large de la grille" + +#: printrun/pronterface.py:807 +msgid "Background color" +msgstr "Couleur de fond" + +#: printrun/pronterface.py:807 +msgid "Pronterface background color" +msgstr "Couleur de fond de Pronterface" + +#: printrun/pronterface.py:808 +#, fuzzy +msgid "3D view background color" +msgstr "Couleur de fond" + +#: printrun/pronterface.py:808 +msgid "Color of the 3D view background" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "3D view travel moves color" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "Color of travel moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "3D view print moves color" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "Color of print moves with tool 0 in 3D view" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "3D view tool 1 moves color" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "Color of print moves with tool 1 in 3D view" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "3D view printed moves color" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "Color of printed moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "3D view current layer moves color" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "Color of moves in current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "3D view printed current layer moves color" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "Color of already printed moves from current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:815 +msgid "Changing most settings here will require restart to get effect" +msgstr "" +"La plupart de ces réglages nécessitent de redémarrer le logiciel pour être " +"appliqués." + +#: printrun/pronterface.py:815 +msgid "Note:" +msgstr "Note:" + +#: printrun/pronterface.py:822 +msgid "automatically try to connect to printer on startup" +msgstr "tenter de se connecter automatiquement à l'imprimante au démarrage" + +#: printrun/pronterface.py:835 printrun/pronterface.py:1316 +msgid "Failed to load recent files list:" +msgstr "" + +#: printrun/pronterface.py:916 +msgid "SD upload: %04.2f%% |" +msgstr "Envoi SD : %04.2f%% |" + +#: printrun/pronterface.py:917 printrun/pronterface.py:922 +msgid " Line# %d of %d lines |" +msgstr " Ligne# %d sur %d lignes |" + +#: printrun/pronterface.py:919 +msgid "SD printing: %04.2f%% |" +msgstr "Impression SD : %04.2f%% |" + +#: printrun/pronterface.py:921 +msgid "Printing: %04.2f%% |" +msgstr "Impression : %04.2f%% |" + +#: printrun/pronterface.py:924 +msgid " Est: %s of %s remaining | " +msgstr " ETA: %s restant sur %s | " + +#: printrun/pronterface.py:926 +msgid " Z: %.3f mm" +msgstr " Z: %.3f mm" + +#: printrun/pronterface.py:931 +msgid "Disconnecting after 4 failed writes." +msgstr "Déconnexion suite à 4 envois échoués." + +#: printrun/pronterface.py:972 +msgid "Locking interface." +msgstr "Verrouillage de l'interface." + +#: printrun/pronterface.py:976 +msgid "Unlocking interface." +msgstr "Déverrouillage de l'interface." + +#: printrun/pronterface.py:985 +msgid "Connecting..." +msgstr "Connexion en cours..." + +#: printrun/pronterface.py:997 +msgid "Could not parse baud rate: " +msgstr "Impossible d'analyser le débit:" + +#: printrun/pronterface.py:1013 printrun/pronterface.py:1023 +msgid "Error: You are trying to connect to a non-existing port." +msgstr "Erreur: Vous essayez de vous connecter à un port inexistant." + +#: printrun/pronterface.py:1015 +msgid "Error: You don't have permission to open %s." +msgstr "Erreur: Vous n'avez pas la permission d'ouvrir %s." + +#: printrun/pronterface.py:1016 +msgid "You might need to add yourself to the dialout group." +msgstr "" +"Vous avez peut être besoin de vous ajouter au groupe dialout ou similaire." + +#: printrun/pronterface.py:1043 +msgid "Disconnected." +msgstr "Déconnecté." + +#: printrun/pronterface.py:1071 +msgid "Reset." +msgstr "Réinitialisée." + +#: printrun/pronterface.py:1072 +msgid "Are you sure you want to reset the printer?" +msgstr "Etes-vous sûr de vouloir réinitialiser l'imprimante?" + +#: printrun/pronterface.py:1072 +msgid "Reset?" +msgstr "Réinitialiser ?" + +#: printrun/pronterface.py:1093 +msgid "Restart" +msgstr "Recommencer" + +#: printrun/pronterface.py:1126 +msgid "Pick SD filename" +msgstr "Lister les fichiers sur la carte SD" + +#: printrun/pronterface.py:1144 +msgid "File upload complete" +msgstr "Envoi du fichier terminé" + +#: printrun/pronterface.py:1151 +msgid "Print paused at: %s" +msgstr "Impression interrompue à : %s" + +#: printrun/pronterface.py:1163 +msgid "Resume" +msgstr "Reprendre" + +#: printrun/pronterface.py:1166 +msgid "Resuming." +msgstr "Continuons !" + +#: printrun/pronterface.py:1192 +msgid "Pick SD file" +msgstr "Choisir un fichier sur la carte SD" + +#: printrun/pronterface.py:1192 +msgid "Select the file to print" +msgstr "Sélectionnez le fichier à imprimer :" + +#: printrun/pronterface.py:1228 printrun/pronterface.py:1259 +msgid "Slicing " +msgstr "Slicing " + +#: printrun/pronterface.py:1237 +msgid "Failed to execute slicing software: " +msgstr "Une erreur s'est produite lors du slicing : " + +#: printrun/pronterface.py:1244 +msgid "Slicing..." +msgstr "Slicing..." + +#: printrun/pronterface.py:1296 +msgid "Open file to print" +msgstr "Ouvrir un fichier à imprimer" + +#: printrun/pronterface.py:1297 +msgid "" +"OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*." +"gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*" +msgstr "" +"Fichiers OBJ, STL et GCODE (;*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ;)|*." +"gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|Tous les fichiers (*.*)|*.*" + +#: printrun/pronterface.py:1325 +msgid "Could not update recent files list:" +msgstr "" + +#: printrun/pronterface.py:1359 +msgid "Loaded %s, %d lines" +msgstr "%s chargé, %d lignes" + +#: printrun/pronterface.py:1362 +msgid "Load File" +msgstr "Charger un fichier" + +#: printrun/pronterface.py:1376 +#, fuzzy +msgid "%.2fmm of filament used in this print" +msgstr "mm de filament utilisés pour cette impression" + +#: printrun/pronterface.py:1377 +msgid "The print goes:" +msgstr "L'impression va :" + +#: printrun/pronterface.py:1378 +msgid "- from %.2f mm to %.2f mm in X and is %.2f mm wide" +msgstr "- de %.02f mm à %.02f mm en X et mesure %.02f mm de large" + +#: printrun/pronterface.py:1379 +msgid "- from %.2f mm to %.2f mm in Y and is %.2f mm deep" +msgstr "- de %.02f mm à %.02f mm en Y et mesure %.02f mm de profondeur" + +#: printrun/pronterface.py:1380 +msgid "- from %.2f mm to %.2f mm in Z and is %.2f mm high" +msgstr "- de %.02f mm à %.02f mm en Y et mesure %.02f mm de haut" + +#: printrun/pronterface.py:1450 +msgid "Printer is now online." +msgstr "Imprimante connectée." + +#: printrun/pronterface.py:1455 +msgid "Disconnect" +msgstr "Déconnecter" + +#: printrun/pronterface.py:1688 +msgid "click to add new custom button" +msgstr "Ajouter un bouton personnalisé" + +#: printrun/pronterface.py:1694 +msgid "Execute command: " +msgstr "Exécuter la commande :" + +#: printrun/pronterface.py:1714 +msgid "" +"Defines custom button. Usage: button \"title\" [/c \"colour\"] command" +msgstr "" +"Définit des boutons personnalidés. Utilisation : \"Libelle\" [/c " +"\"couleur\"] commande" + +#: printrun/pronterface.py:1736 +msgid "Custom button number should be between 0 and 63" +msgstr "" +"Les numéros des boutons personnalisés doivent être compris entre 0 et 63." + +#: printrun/pronterface.py:1827 +msgid "Edit custom button '%s'" +msgstr "Editer le bouton personnalisé '%s'" + +#: printrun/pronterface.py:1829 +msgid "Move left <<" +msgstr "Déplacer vers la gauche <<" + +#: printrun/pronterface.py:1832 +msgid "Move right >>" +msgstr "Déplacer vers la droite >>" + +#: printrun/pronterface.py:1836 +msgid "Remove custom button '%s'" +msgstr "Supprimer le bouton personnalisé '%s'" + +#: printrun/pronterface.py:1839 +msgid "Add custom button" +msgstr "Ajouter un bouton personnalisé" + +#: printrun/pronterface.py:1972 +msgid "event object missing" +msgstr "événement d'objet manquant" + +#: printrun/pronterface.py:1985 +msgid "Do you want to erase the macro?" +msgstr "Voulez-vous effacer la macro ?" + +#: printrun/pronterface.py:1989 +msgid "Cancelled." +msgstr "Annulé" + +#: printrun/pronterface.py:2007 +msgid "Enter macro name" +msgstr "Saisissez le nom de la macro" + +#: printrun/pronterface.py:2010 +msgid "Macro name:" +msgstr "Nom :" + +#: printrun/pronterface.py:2013 +msgid "Ok" +msgstr "Valider" + +#: printrun/pronterface.py:2035 +msgid "Macro name may contain only ASCII alphanumeric symbols and underscores" +msgstr "" +"Un nom de macro ne peut contenir que des caractères alphanumérique ASCII et " +"des underscore (_)" + +#: printrun/pronterface.py:2038 +msgid "Name '%s' is being used by built-in command" +msgstr "Le nom '%s' est utilisé par une commande interne" + +#: pronsole.py:31 +msgid "Caught an exception, exiting:" +msgstr "Exception attrapée, on quitte:" + +#~ msgid "raw G-Code line \"%s\" could not be parsed" +#~ msgstr "la ligne de G-Code \"%s\" n'a pas pu être analysée" + +#~ msgid "Watch" +#~ msgstr "Surveiller" + +#~ msgid "Monitor Temperatures in Graph" +#~ msgstr "Surveiller les températures avec un graphe" + +#~ msgid "Check current hotend temperature" +#~ msgstr "Vérifier la température actuelle de la buse" + +#~ msgid "Check temp" +#~ msgstr "Lire les températures" + +#~ msgid "Debug G-code" +#~ msgstr "Débugguer le G-code" + +#~ msgid "&?" +#~ msgstr "&?" + +#~ msgid "Compose" +#~ msgstr "Composer" + +#~ msgid "Recover previous Print" +#~ msgstr "Récupérer l'impression précédente" + +#~ msgid "Initialized 3D visualization in %.2f seconds" +#~ msgstr "Visualisation 3D initialisée en %.2f secondes." + +#~ msgid "Vertex count: %d" +#~ msgstr "Nombre de sommets: %d" + +#~ msgid "Enable 3D viewer" +#~ msgstr "Activer la visualisation 3D" + +#~ msgid " SD printing:%04.2f %%" +#~ msgstr " Impression SD : %04.2f %%" + +#~ msgid "Folder of last opened file" +#~ msgstr "Dossier du dernier fichier ouvert" + +#~ msgid "Mini mode" +#~ msgstr "Mode réduit" + +#~ msgid "Full mode" +#~ msgstr "Mode complet" + +#~ msgid "Paused." +#~ msgstr "En pause." + +#~ msgid "Failed to start web interface" +#~ msgstr "Échec du lancement de l'interface web" + +#~ msgid "CherryPy is not installed. Web Interface Disabled." +#~ msgstr "CherryPy n'est pas installé. L'interface web est désactivée." + +#~ msgid ", %d lines" +#~ msgstr ", %d lignes" diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/it/LC_MESSAGES/plater.mo Binary file printrun-src/locale/it/LC_MESSAGES/plater.mo has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/it/LC_MESSAGES/plater.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/it/LC_MESSAGES/plater.po Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,92 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2012-01-09 15:07+CET\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Italian RepRap Community \n" +"Language-Team: Italian RepRap Community \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + + +#: plater.py:223 +msgid "Plate building tool" +msgstr "Strumento di posizionamento sul piatto" + +#: plater.py:229 +msgid "Clear" +msgstr "Pulisci" + +#: plater.py:230 +msgid "Load" +msgstr "Carica" + +#: plater.py:232 +msgid "Export" +msgstr "Esporta" + +#: plater.py:235 +msgid "Done" +msgstr "Fatto" + +#: plater.py:237 +msgid "Cancel" +msgstr "Cancella" + +#: plater.py:239 +msgid "Snap to Z = 0" +msgstr "Vai a Z = 0" + +#: plater.py:240 +msgid "Put at 100, 100" +msgstr "Metti a 100,100" + +#: plater.py:241 +msgid "Delete" +msgstr "Elimina" + +#: plater.py:242 +msgid "Auto" +msgstr "Automatico" + +#: plater.py:266 +msgid "Autoplating" +msgstr "Posizionamento automatico sul piatto" + +#: plater.py:294 +msgid "Bed full, sorry sir :(" +msgstr "Il letto è pieno, mi dispiace :(" + +#: plater.py:304 +msgid "Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "Sei sicuro di voler pulire la griglia? Tutte le modifiche non salvate saranno perse." + +#: plater.py:304 +msgid "Clear the grid?" +msgstr "Pulire la griglia?" + +#: plater.py:346 +msgid "Pick file to save to" +msgstr "Scegli un file in cui salvare" + +#: plater.py:347 +msgid "STL files (;*.stl;)" +msgstr "files STL (;*.stl;)" + +#: plater.py:367 +msgid "wrote " +msgstr "scritti " + +#: plater.py:370 +msgid "Pick file to load" +msgstr "Scegli un file da caricare" + +#: plater.py:371 +msgid "STL files (;*.stl;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" +msgstr "files STL (;*.stl;)|*.stl|files OpenSCAD (;*.scad;)|*.scad" diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/it/LC_MESSAGES/pronterface.mo Binary file printrun-src/locale/it/LC_MESSAGES/pronterface.mo has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/it/LC_MESSAGES/pronterface.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/it/LC_MESSAGES/pronterface.po Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,2216 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2014-03-30 14:35+CEST\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Italian RepRap Community \n" +"Language-Team: Italian RepRap Community \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + +#: printrun/excluder.py:26 +msgid "" +"Part excluder: draw rectangles where print instructions should be ignored" +msgstr "" + +#: printrun/excluder.py:27 printrun/excluder.py:29 +msgid "Reset selection" +msgstr "" + +#: printrun/gcodeplater.py:43 printrun/gcodeplater.py:44 +msgid "GCODE files (*.gcode;*.GCODE;*.g)" +msgstr "" + +#: printrun/gcodeplater.py:112 printrun/gcodeplater.py:152 +msgid "Warning: no rotation support for now, object won't be correctly rotated" +msgstr "" + +#: printrun/gcodeplater.py:141 printrun/gcodeplater.py:178 +msgid "Exported merged G-Codes to %s" +msgstr "" + +#: printrun/gcview.py:352 +msgid "Fit to plate" +msgstr "" + +#: printrun/gcview.py:353 +msgid "Fit to plate [F]" +msgstr "" + +#: printrun/gui/__init__.py:19 printrun/pronterface.py:45 +msgid "WX is not installed. This program requires WX to run." +msgstr "WX non è installato. Questo software richiede WX per funzionare." + +#: printrun/gui/__init__.py:159 +#, fuzzy +msgid "Commands" +msgstr "Comando" + +#: printrun/gui/__init__.py:160 +msgid "Status" +msgstr "" + +#: printrun/gui/controls.py:126 +#, fuzzy +msgid "Heat:" +msgstr "Estrusore:" + +#: printrun/gui/controls.py:129 +msgid "Switch Hotend Off" +msgstr "" + +#: printrun/gui/controls.py:129 printrun/gui/controls.py:149 +#: printrun/gui/toolbar.py:85 +msgid "Off" +msgstr "Off" + +#: printrun/gui/controls.py:137 +#, fuzzy +msgid "Select Temperature for Hotend" +msgstr "Ultima temperatura dell'estrusore" + +#: printrun/gui/controls.py:141 +msgid "Switch Hotend On" +msgstr "" + +#: printrun/gui/controls.py:141 printrun/gui/controls.py:161 +#: printrun/gui/controls.py:198 +msgid "Set" +msgstr "On" + +#: printrun/gui/controls.py:146 printrun/gui/controls.py:215 +msgid "Bed:" +msgstr "Piano:" + +#: printrun/gui/controls.py:149 +msgid "Switch Heated Bed Off" +msgstr "" + +#: printrun/gui/controls.py:157 +#, fuzzy +msgid "Select Temperature for Heated Bed" +msgstr "Ultima temperatura impostata per il letto riscaldato" + +#: printrun/gui/controls.py:161 +msgid "Switch Heated Bed On" +msgstr "" + +#: printrun/gui/controls.py:187 +msgid "Print speed:" +msgstr "" + +#: printrun/gui/controls.py:192 printrun/gui/controls.py:207 +msgid "%d%%" +msgstr "" + +#: printrun/gui/controls.py:198 +msgid "Set print speed factor" +msgstr "" + +#: printrun/gui/controls.py:213 +msgid "Heater:" +msgstr "Estrusore:" + +#: printrun/gui/controls.py:265 +msgid "Length:" +msgstr "" + +#: printrun/gui/controls.py:267 +msgid "mm" +msgstr "mm" + +#: printrun/gui/controls.py:267 +msgid "mm @" +msgstr "" + +#: printrun/gui/controls.py:269 +msgid "Amount to Extrude or Retract (mm)" +msgstr "" + +#: printrun/gui/controls.py:272 +msgid "Extrude / Retract speed (mm/min)" +msgstr "" + +#: printrun/gui/controls.py:278 +msgid "Speed:" +msgstr "" + +#: printrun/gui/controls.py:279 +msgid "" +"mm/\n" +"min" +msgstr "" + +#: printrun/gui/controls.py:290 +msgid "Tool:" +msgstr "" + +#: printrun/gui/controls.py:293 +msgid "Click to switch current extruder" +msgstr "" + +#: printrun/gui/controls.py:310 +msgid "Select current extruder" +msgstr "" + +#: printrun/gui/controls.py:371 +msgid "Set Maximum Speed for X & Y axes (mm/min)" +msgstr "" + +#: printrun/gui/controls.py:372 +msgid "XY:" +msgstr "XY:" + +#: printrun/gui/controls.py:374 +#, fuzzy +msgid "mm/min Z:" +msgstr "mm/min" + +#: printrun/gui/controls.py:376 +msgid "Set Maximum Speed for Z axis (mm/min)" +msgstr "" + +#: printrun/gui/graph.py:28 +msgid "Temperature graph" +msgstr "" + +#: printrun/gui/log.py:32 +msgid "" +"Send commands to printer\n" +"(Type 'help' for simple\n" +"help function)" +msgstr "" + +#: printrun/gui/log.py:39 +msgid "Send" +msgstr "Invia" + +#: printrun/gui/log.py:39 +#, fuzzy +msgid "Send Command to Printer" +msgstr "Controllo automatico temperatura" + +#: printrun/gui/toolbar.py:23 +msgid "Lock" +msgstr "" + +#: printrun/gui/toolbar.py:25 +msgid "Lock graphical interface" +msgstr "" + +#: printrun/gui/toolbar.py:32 +msgid "" +"Communication Settings\n" +"Click to rescan ports" +msgstr "" + +#: printrun/gui/toolbar.py:32 +msgid "Port" +msgstr "Porta" + +#: printrun/gui/toolbar.py:37 +msgid "Select Port Printer is connected to" +msgstr "" + +#: printrun/gui/toolbar.py:46 +msgid "Select Baud rate for printer communication" +msgstr "" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1052 +msgid "Connect" +msgstr "Connetti" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1053 +msgid "Connect to the printer" +msgstr "Connetti alla stampante" + +#: printrun/gui/toolbar.py:61 +msgid "Reset" +msgstr "Reset" + +#: printrun/gui/toolbar.py:61 +#, fuzzy +msgid "Reset the printer" +msgstr "Connetti alla stampante" + +#: printrun/gui/toolbar.py:69 +#, fuzzy +msgid "Load a 3D model file" +msgstr "Carica file" + +#: printrun/gui/toolbar.py:69 +msgid "Load file" +msgstr "Carica file" + +#: printrun/gui/toolbar.py:70 +msgid "SD" +msgstr "SD" + +#: printrun/gui/toolbar.py:70 +#, fuzzy +msgid "SD Card Printing" +msgstr "Stampa SD" + +#: printrun/gui/toolbar.py:74 +msgid "Start Printing Loaded File" +msgstr "" + +#: printrun/gui/toolbar.py:74 printrun/pronterface.py:1003 +#: printrun/pronterface.py:1062 printrun/pronterface.py:1078 +#: printrun/pronterface.py:1363 printrun/pronterface.py:1445 +msgid "Print" +msgstr "Stampa" + +#: printrun/gui/toolbar.py:80 +msgid "Pause Current Print" +msgstr "" + +#: printrun/gui/toolbar.py:80 printrun/pronterface.py:1002 +#: printrun/pronterface.py:1061 printrun/pronterface.py:1081 +#: printrun/pronterface.py:1091 printrun/pronterface.py:1172 +#: printrun/pronterface.py:1364 +msgid "Pause" +msgstr "Pausa" + +#: printrun/gui/toolbar.py:85 +msgid "Turn printer off" +msgstr "" + +#: printrun/gui/viz.py:65 +msgid "" +"Click to examine / edit\n" +" layers of loaded file" +msgstr "" + +#: printrun/gui/widgets.py:36 +msgid "Find" +msgstr "" + +#: printrun/gui/widgets.py:38 +msgid "Save" +msgstr "Salva" + +#: printrun/gui/widgets.py:43 printrun/objectplater.py:76 +#: printrun/pronterface.py:1257 printrun/pronterface.py:2017 +msgid "Cancel" +msgstr "Annulla" + +#: printrun/gui/widgets.py:70 +#, fuzzy +msgid "Not Found!" +msgstr "File non trovato!" + +#: printrun/gui/widgets.py:124 +#, fuzzy +msgid "Printer settings" +msgstr "Modifica impostazioni" + +#: printrun/gui/widgets.py:125 +#, fuzzy +msgid "User interface" +msgstr "Interfaccia di stampa" + +#: printrun/gui/widgets.py:126 +#, fuzzy +msgid "Colors" +msgstr "Colore" + +#: printrun/gui/widgets.py:127 +#, fuzzy +msgid "External commands" +msgstr "Esegui comando: " + +#: printrun/gui/widgets.py:132 +msgid "Edit settings" +msgstr "Modifica impostazioni" + +#: printrun/gui/widgets.py:135 +#, fuzzy +msgid "Settings" +msgstr "&Impostazioni" + +#: printrun/gui/widgets.py:187 +msgid "Custom button" +msgstr "Personalizza bottone" + +#: printrun/gui/widgets.py:193 +msgid "Button title" +msgstr "Titolo bottone" + +#: printrun/gui/widgets.py:196 +msgid "Command" +msgstr "Comando" + +#: printrun/gui/widgets.py:205 +msgid "Color" +msgstr "Colore" + +#: printrun/gviz.py:29 +msgid "Layer number and Z position show here when you scroll" +msgstr "" + +#: printrun/gviz.py:39 +msgid "Zoom In [+]" +msgstr "" + +#: printrun/gviz.py:40 +msgid "Zoom Out [-]" +msgstr "" + +#: printrun/gviz.py:42 +msgid "Move Up a Layer [U]" +msgstr "" + +#: printrun/gviz.py:43 +msgid "Move Down a Layer [D]" +msgstr "" + +#: printrun/gviz.py:44 +#, fuzzy +msgid "Reset view" +msgstr "Reset" + +#: printrun/gviz.py:68 +msgid "Gcode view, shift to move view, mousewheel to set layer" +msgstr "" + +#: printrun/gviz.py:105 printrun/gviz.py:241 +msgid "Layer %d - Going Up - Z = %.03f mm" +msgstr "" + +#: printrun/gviz.py:250 +msgid "Layer %d - Going Down - Z = %.03f mm" +msgstr "" + +#: printrun/objectplater.py:35 +msgid "Plate building tool" +msgstr "" + +#: printrun/objectplater.py:45 +msgid "Clear" +msgstr "" + +#: printrun/objectplater.py:49 +#, fuzzy +msgid "Load" +msgstr "Caricato " + +#: printrun/objectplater.py:53 +msgid "Snap to Z = 0" +msgstr "" + +#: printrun/objectplater.py:57 +msgid "Put at center" +msgstr "" + +#: printrun/objectplater.py:61 +msgid "Delete" +msgstr "" + +#: printrun/objectplater.py:65 +msgid "Auto arrange" +msgstr "" + +#: printrun/objectplater.py:69 +msgid "Export" +msgstr "" + +#: printrun/objectplater.py:73 +msgid "Done" +msgstr "" + +#: printrun/objectplater.py:151 +msgid "Autoplating" +msgstr "" + +#: printrun/objectplater.py:199 +msgid "Bed full, sorry sir :(" +msgstr "" + +#: printrun/objectplater.py:209 +msgid "" +"Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "" + +#: printrun/objectplater.py:210 +#, fuzzy +msgid "Clear the grid?" +msgstr " Chiude la finestra" + +#: printrun/objectplater.py:256 +#, fuzzy +msgid "Pick file to load" +msgstr "Scegli un file dalla scheda SD" + +#: printrun/objectplater.py:267 +#, fuzzy +msgid "Pick file to save to" +msgstr "Scegli un file dalla scheda SD" + +#: printrun/plater.py:212 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL|OpenSCAD files (*.scad)|*.scad" +msgstr "" + +#: printrun/plater.py:213 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL" +msgstr "" + +#: printrun/plater.py:244 +#, fuzzy +msgid "Loading STL file failed" +msgstr "Apertura del file fallita." + +#: printrun/plater.py:244 printrun/plater.py:251 +msgid "Error" +msgstr "" + +#: printrun/plater.py:251 +#, fuzzy +msgid "Loading OpenSCAD file failed" +msgstr "Apertura del file fallita." + +#: printrun/plater.py:284 +msgid "Couldn't load non-existing file %s" +msgstr "" + +#: printrun/plater.py:360 +msgid "Wrote plate to %s" +msgstr "" + +#: printrun/plater.py:367 +msgid "" +"Failed to use simarrange for plating, falling back to the standard method" +msgstr "" + +#: printrun/plater.py:372 +msgid "Autoplating using simarrange" +msgstr "" + +#: printrun/plater.py:387 +msgid "Plate full, please remove some objects" +msgstr "" + +#: printrun/plater.py:404 +msgid "simarrange failed" +msgstr "" + +#: printrun/printcore.py:177 +msgid "Could not connect to %s:%s:" +msgstr "" + +#: printrun/printcore.py:178 +msgid "Socket error %s:" +msgstr "" + +#: printrun/printcore.py:191 printrun/printcore.py:196 +msgid "Could not connect to %s at baudrate %s:" +msgstr "" + +#: printrun/printcore.py:192 +msgid "Serial error: %s" +msgstr "" + +#: printrun/printcore.py:197 +msgid "IO error: %s" +msgstr "" + +#: printrun/printcore.py:231 +msgid "Can't read from printer (disconnected?) (SelectError {0}): {1}" +msgstr "" + +#: printrun/printcore.py:234 +msgid "SelectError ({0}): {1}" +msgstr "" + +#: printrun/printcore.py:237 +msgid "Can't read from printer (disconnected?) (SerialException): {0}" +msgstr "" + +#: printrun/printcore.py:240 +msgid "Can't read from printer (disconnected?) (Socket error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:245 +msgid "Can't read from printer (disconnected?) (OS Error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:259 +msgid "Aborting connection attempt after 4 failed writes." +msgstr "" + +#: printrun/printcore.py:462 printrun/printcore.py:470 +#: printrun/pronsole.py:1192 printrun/pronsole.py:1250 +#: printrun/pronterface.py:209 printrun/pronterface.py:962 +#: printrun/pronterface.py:1111 printrun/pronterface.py:1178 +msgid "Not connected to printer." +msgstr "Non connesso alla stampante." + +#: printrun/printcore.py:479 +msgid "Print start callback failed with:" +msgstr "" + +#: printrun/printcore.py:490 +msgid "Print end callback failed with:" +msgstr "" + +#: printrun/printcore.py:493 +msgid "Print thread died due to the following error:" +msgstr "" + +#: printrun/printcore.py:584 +msgid "Could not analyze command %s:" +msgstr "" + +#: printrun/printcore.py:601 +msgid "Can't write to printer (disconnected ?):" +msgstr "" + +#: printrun/printcore.py:604 +msgid "Can't write to printer (disconnected?) (Socket error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:607 +msgid "Can't write to printer (disconnected?) (SerialException): {0}" +msgstr "" + +#: printrun/printcore.py:610 +msgid "Socket connection broken, disconnected. ({0}): {1}" +msgstr "" + +#: printrun/pronsole.py:72 +#, fuzzy +msgid "Default: " +msgstr "Valori di default" + +#: printrun/pronsole.py:73 +msgid "(Control-doubleclick to reset to default value)" +msgstr "" + +#: printrun/pronsole.py:108 +#, fuzzy +msgid "" +"Are you sure you want to reset the setting to the default value: {0!r} ?" +msgstr "Sei sicuro di voler resettare la stampante?" + +#: printrun/pronsole.py:108 +msgid "Confirm set default" +msgstr "" + +#: printrun/pronsole.py:258 +msgid "Width" +msgstr "" + +#: printrun/pronsole.py:261 +msgid "Depth" +msgstr "" + +#: printrun/pronsole.py:264 +msgid "Height" +msgstr "" + +#: printrun/pronsole.py:267 +msgid "X offset" +msgstr "" + +#: printrun/pronsole.py:270 +msgid "Y offset" +msgstr "" + +#: printrun/pronsole.py:273 +msgid "Z offset" +msgstr "" + +#: printrun/pronsole.py:276 +msgid "X home pos." +msgstr "" + +#: printrun/pronsole.py:279 +msgid "Y home pos." +msgstr "" + +#: printrun/pronsole.py:282 +msgid "Z home pos." +msgstr "" + +#: printrun/pronsole.py:301 +msgid "Port used to communicate with printer" +msgstr "Porta usata per comunicare con la stampante" + +#: printrun/pronsole.py:301 +msgid "Serial port" +msgstr "" + +#: printrun/pronsole.py:302 +msgid "Baud rate" +msgstr "" + +#: printrun/pronsole.py:302 +#, fuzzy +msgid "Communications Speed" +msgstr "Velocità di comunicazione (default: 115200)" + +#: printrun/pronsole.py:303 +msgid "TCP streaming mode" +msgstr "" + +#: printrun/pronsole.py:303 +msgid "" +"When using a TCP connection to the printer, the streaming mode will not wait " +"for acks from the printer to send new commands. This will break things such " +"as ETA prediction, but can result in smoother prints." +msgstr "" + +#: printrun/pronsole.py:304 +msgid "Bed temperature for ABS" +msgstr "" + +#: printrun/pronsole.py:304 +#, fuzzy +msgid "Heated Build Platform temp for ABS (deg C)" +msgstr "Temperatura piano di stampa per ABS (default: 110° C) " + +#: printrun/pronsole.py:305 +msgid "Bed temperature for PLA" +msgstr "" + +#: printrun/pronsole.py:305 +#, fuzzy +msgid "Heated Build Platform temp for PLA (deg C)" +msgstr "Temperatura piano di stampa per PLA (default: 60° C)" + +#: printrun/pronsole.py:306 +#, fuzzy +msgid "Extruder temp for ABS (deg C)" +msgstr "Temperatura di estrusione per ABS (default: 230° C)" + +#: printrun/pronsole.py:306 +#, fuzzy +msgid "Extruder temperature for ABS" +msgstr "Temperatura di estrusione per ABS (default: 230° C)" + +#: printrun/pronsole.py:307 +#, fuzzy +msgid "Extruder temp for PLA (deg C)" +msgstr "Temperatura di estrusione per PLA (default: 185° C" + +#: printrun/pronsole.py:307 +#, fuzzy +msgid "Extruder temperature for PLA" +msgstr "Temperatura di estrusione per PLA (default: 185° C" + +#: printrun/pronsole.py:308 +#, fuzzy +msgid "Feedrate for Control Panel Moves in X and Y (mm/min)" +msgstr "" +"Velocità dei movimenti degli assi X e Y in modalità manuale (default: 3000mm/" +"min)" + +#: printrun/pronsole.py:308 +msgid "X && Y manual feedrate" +msgstr "" + +#: printrun/pronsole.py:309 +#, fuzzy +msgid "Feedrate for Control Panel Moves in Z (mm/min)" +msgstr "" +"Velocità dei movimenti dell'asse Z in modalità manuale (default: 200mm/min)" + +#: printrun/pronsole.py:309 +msgid "Z manual feedrate" +msgstr "" + +#: printrun/pronsole.py:310 +msgid "E manual feedrate" +msgstr "" + +#: printrun/pronsole.py:310 +#, fuzzy +msgid "Feedrate for Control Panel Moves in Extrusions (mm/min)" +msgstr "" +"Velocità dei movimenti dell'estrusore in modalità manuale (default: 300mm/" +"min)" + +#: printrun/pronsole.py:311 +#, fuzzy +msgid "Slice command" +msgstr "Esegui comando: " + +#: printrun/pronsole.py:312 +#, fuzzy +msgid "Slice settings command" +msgstr "Impostazioni di generazione del percorso" + +#: printrun/pronsole.py:312 +msgid "Slicer options command" +msgstr "" + +#: printrun/pronsole.py:313 +msgid "Executable to run when the print is finished" +msgstr "" + +#: printrun/pronsole.py:313 +#, fuzzy +msgid "Final command" +msgstr "Comando" + +#: printrun/pronsole.py:314 +#, fuzzy +msgid "Error command" +msgstr "Esegui comando: " + +#: printrun/pronsole.py:314 +msgid "Executable to run when an error occurs" +msgstr "" + +#: printrun/pronsole.py:381 +msgid "Failed to run callback after setting \"%s\":" +msgstr "" + +#: printrun/pronsole.py:469 +msgid "Build dimensions" +msgstr "" + +#: printrun/pronsole.py:469 +#, fuzzy +msgid "" +"Dimensions of Build Platform\n" +" & optional offset of origin\n" +" & optional switch position\n" +"\n" +"Examples:\n" +" XXXxYYY\n" +" XXX,YYY,ZZZ\n" +" XXXxYYYxZZZ+OffX+OffY+OffZ\n" +"XXXxYYYxZZZ+OffX+OffY+OffZ+HomeX+HomeY+HomeZ" +msgstr "" +"Dimensioni della superficie di stampa\n" +" & e (opzionale) spostamento del centro\n" +"\n" +"Esempi:\n" +" XXXxYYY\n" +" XXX,YYY,ZZZ\n" +" XXXxYYYxZZZ+SposX+SposY+SposZ" + +#: printrun/pronsole.py:495 +msgid "" +"Welcome to the printer console! Type \"help\" for a list of available " +"commands." +msgstr "" + +#: printrun/pronsole.py:657 printrun/pronsole.py:665 printrun/pronsole.py:673 +#: printrun/pronsole.py:1519 printrun/pronsole.py:1546 +#: printrun/pronsole.py:1618 printrun/pronterface.py:357 +#: printrun/pronterface.py:377 printrun/pronterface.py:394 +msgid "Printer is not online." +msgstr "La stampante non è connessa." + +#: printrun/pronsole.py:692 +msgid "Exiting program. Goodbye!" +msgstr "" + +#: printrun/pronsole.py:697 +msgid "Disconnects from the printer and exits the program." +msgstr "" + +#: printrun/pronsole.py:974 +msgid "" +"load this file on startup instead of .pronsolerc ; you may chain config " +"files, if so settings auto-save will use the last specified file" +msgstr "" + +#: printrun/pronsole.py:975 +msgid "" +"executes command after configuration/.pronsolerc is loaded ; macros/settings " +"from these commands are not autosaved" +msgstr "" + +#: printrun/pronsole.py:976 +msgid "file to load" +msgstr "" + +#: printrun/pronsole.py:1104 +#, fuzzy +msgid "Loaded %s, %d lines." +msgstr "Caricato %s, %d linee" + +#: printrun/pronsole.py:1105 printrun/pronterface.py:1381 +#, fuzzy +msgid "Estimated duration: %d layers, %s" +msgstr "Durata stimata (pessimistica): " + +#: printrun/pronsole.py:1134 +msgid "No file name given." +msgstr "" + +#: printrun/pronsole.py:1140 +msgid "Skeining file: %s" +msgstr "" + +#: printrun/pronsole.py:1142 printrun/pronterface.py:1305 +msgid "File not found!" +msgstr "File non trovato!" + +#: printrun/pronsole.py:1147 +msgid "Entering slicer settings: %s" +msgstr "" + +#: printrun/pronsole.py:1151 +#, fuzzy +msgid "Slicing: " +msgstr "Generazione del percorso " + +#: printrun/pronsole.py:1158 +#, fuzzy +msgid "Loading sliced file." +msgstr "Carica file" + +#: printrun/pronsole.py:1161 +#, fuzzy +msgid "Slicing failed: %s" +msgstr "Generazione del percorso " + +#: printrun/pronsole.py:1174 +msgid "" +"Creates a gcode file from an stl model using the slicer (with tab-completion)" +msgstr "" + +#: printrun/pronsole.py:1175 +msgid "slice filename.stl - create gcode file" +msgstr "" + +#: printrun/pronsole.py:1176 +msgid "" +"slice filename.stl view - create gcode file and view using skeiniso (if " +"using skeinforge)" +msgstr "" + +#: printrun/pronsole.py:1177 +#, fuzzy +msgid "slice set - adjust slicer settings" +msgstr " Configura la generazione del percorso" + +#: printrun/pronsole.py:1189 +msgid "Please enter target name in 8.3 format." +msgstr "" + +#: printrun/pronsole.py:1195 +msgid "Uploading as %s" +msgstr "" + +#: printrun/pronsole.py:1196 +msgid "Uploading %s" +msgstr "" + +#: printrun/pronsole.py:1198 +msgid "Press Ctrl-C to interrupt upload." +msgstr "" + +#: printrun/pronsole.py:1201 +msgid "Progress: " +msgstr "" + +#: printrun/pronsole.py:1212 +msgid "Upload completed. %s should now be on the card." +msgstr "" + +#: printrun/pronsole.py:1216 +msgid "...interrupted!" +msgstr "" + +#: printrun/pronsole.py:1218 +msgid "Something wrong happened while uploading:" +msgstr "" + +#: printrun/pronsole.py:1224 +msgid "A partial file named %s may have been written to the sd card." +msgstr "" + +#: printrun/pronsole.py:1241 +msgid "" +"Send a loaded gcode file to the printer. Load a file with the load command " +"first." +msgstr "" + +#: printrun/pronsole.py:1243 +msgid "Send a loaded gcode file to the printer. You have %s loaded right now." +msgstr "" + +#: printrun/pronsole.py:1247 printrun/pronterface.py:730 +#: printrun/pronterface.py:1108 +msgid "No file loaded. Please use load first." +msgstr "Nessub file caricato. Usare Apri prima." + +#: printrun/pronsole.py:1252 +#, fuzzy +msgid "Printing %s" +msgstr " Stampa in corso:%04.2f %% |" + +#: printrun/pronsole.py:1253 +msgid "You can monitor the print with the monitor command." +msgstr "" + +#: printrun/pronsole.py:1261 +msgid "Not printing, cannot pause." +msgstr "" + +#: printrun/pronsole.py:1267 +#, fuzzy +msgid "Pauses a running print" +msgstr "Inizio della stampa" + +#: printrun/pronsole.py:1274 +msgid "Not paused, unable to resume. Start a print first." +msgstr "" + +#: printrun/pronsole.py:1284 +msgid "Resumes a paused print." +msgstr "" + +#: printrun/pronsole.py:1293 +msgid "Files on SD card:" +msgstr "" + +#: printrun/pronsole.py:1307 printrun/pronsole.py:1349 +#: printrun/pronsole.py:1562 +#, fuzzy +msgid "Printer is not online. Please connect to it first." +msgstr "La stampante non è connessa." + +#: printrun/pronsole.py:1312 +msgid "Lists files on the SD card" +msgstr "" + +#: printrun/pronsole.py:1316 printrun/pronterface.py:1645 +msgid "Opening file failed." +msgstr "Apertura del file fallita." + +#: printrun/pronsole.py:1322 printrun/pronterface.py:1651 +msgid "Starting print" +msgstr "Inizio della stampa" + +#: printrun/pronsole.py:1345 +#, fuzzy +msgid "Resets the printer." +msgstr "Connetti alla stampante" + +#: printrun/pronsole.py:1355 +#, fuzzy +msgid "File is not present on card. Please upload it first." +msgstr "Nessub file caricato. Usare Apri prima." + +#: printrun/pronsole.py:1359 +msgid "Printing file: %s from SD card." +msgstr "" + +#: printrun/pronsole.py:1360 +msgid "Requesting SD print..." +msgstr "" + +#: printrun/pronsole.py:1364 +msgid "Print a file from the SD card. Tab completes with available file names." +msgstr "" + +#: printrun/pronsole.py:1365 +msgid "sdprint filename.g" +msgstr "" + +#: printrun/pronsole.py:1382 +msgid "Print resumed at: %s" +msgstr "" + +#: printrun/pronsole.py:1384 +msgid "Print started at: %s" +msgstr "" + +#: printrun/pronsole.py:1392 +msgid "Failed to inhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1399 +msgid "Failed to uninhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1403 +msgid "Print ended at: %(end_time)s and took %(duration)s" +msgstr "" + +#: printrun/pronsole.py:1457 +msgid "Printer is not currently printing. No ETA available." +msgstr "" + +#: printrun/pronsole.py:1460 +#, fuzzy +msgid "Est: %s of %s remaining" +msgstr " Stima: %s di %s rimanente | " + +#: printrun/pronsole.py:1465 +msgid "Displays estimated remaining print time." +msgstr "" + +#: printrun/pronsole.py:1498 +#, fuzzy +msgid "Read the extruder and bed temperature." +msgstr "Devi inserire una temperatura." + +#: printrun/pronsole.py:1507 printrun/pronsole.py:1540 +msgid "You must enter a temperature." +msgstr "Devi inserire una temperatura." + +#: printrun/pronsole.py:1512 +msgid "" +"%s is a high temperature to set your extruder to. Are you sure you want to " +"do that?" +msgstr "" + +#: printrun/pronsole.py:1517 +#, fuzzy +msgid "Setting hotend temperature to %s degrees Celsius." +msgstr "Imposto la temperatura dell'estrusore a %f gradi Celsius." + +#: printrun/pronsole.py:1521 printrun/pronterface.py:359 +msgid "" +"You cannot set negative temperatures. To turn the hotend off entirely, set " +"its temperature to 0." +msgstr "" +"Non è possibile impostare temperature negative. Per raffreddare l'ugello " +"imposta la sua temperatura a 0." + +#: printrun/pronsole.py:1524 +#, fuzzy +msgid "Sets the hotend temperature to the value entered." +msgstr "Imposto la temperatura dell'estrusore a %f gradi Celsius." + +#: printrun/pronsole.py:1525 printrun/pronsole.py:1552 +msgid "Enter either a temperature in celsius or one of the following keywords" +msgstr "" + +#: printrun/pronsole.py:1544 +#, fuzzy +msgid "Setting bed temperature to %s degrees Celsius." +msgstr "Imposto la temperatura del piano di stampa a %f gradi Celsius." + +#: printrun/pronsole.py:1548 printrun/pronterface.py:379 +msgid "" +"You cannot set negative temperatures. To turn the bed off entirely, set its " +"temperature to 0." +msgstr "" +"Non è possibile impostare temperature negative. Per raffreddare il piano di " +"stampa imposta la sua temperatura a 0." + +#: printrun/pronsole.py:1551 +#, fuzzy +msgid "Sets the bed temperature to the value entered." +msgstr "Imposto la temperatura del piano di stampa a %f gradi Celsius." + +#: printrun/pronsole.py:1565 +msgid "Printer is not printing. Please print something before monitoring." +msgstr "" + +#: printrun/pronsole.py:1567 +#, fuzzy +msgid "Monitoring printer, use ^C to interrupt." +msgstr "Sto controllando la stampante." + +#: printrun/pronsole.py:1572 printrun/pronterface.py:329 +msgid "Invalid period given." +msgstr "Periodo non valido." + +#: printrun/pronsole.py:1573 +msgid "Updating values every %f seconds." +msgstr "" + +#: printrun/pronsole.py:1584 printrun/pronsole.py:1587 +msgid "Print progress: " +msgstr "" + +#: printrun/pronsole.py:1595 printrun/pronterface.py:333 +msgid "Done monitoring." +msgstr "Controllo terminato." + +#: printrun/pronsole.py:1599 +msgid "Monitor a machine's temperatures and an SD print's status." +msgstr "" + +#: printrun/pronsole.py:1600 +msgid "" +"monitor - Reports temperature and SD print status (if SD printing) every 5 " +"seconds" +msgstr "" + +#: printrun/pronsole.py:1601 +msgid "" +"monitor 2 - Reports temperature and SD print status (if SD printing) every 2 " +"seconds" +msgstr "" + +#: printrun/pronsole.py:1612 +msgid "You must specify the tool index as an integer." +msgstr "" + +#: printrun/pronsole.py:1616 +msgid "Using tool %d." +msgstr "" + +#: printrun/pronsole.py:1620 +msgid "You cannot set negative tool numbers." +msgstr "" + +#: printrun/pronsole.py:1623 +msgid "" +"Switches to the specified tool (e.g. doing tool 1 will emit a T1 G-Code)." +msgstr "" + +#: printrun/pronsole.py:1627 +msgid "No move specified." +msgstr "" + +#: printrun/pronsole.py:1630 printrun/pronsole.py:1692 +#: printrun/pronsole.py:1735 printrun/pronsole.py:1762 +msgid "" +"Printer is currently printing. Please pause the print before you issue " +"manual commands." +msgstr "" + +#: printrun/pronsole.py:1633 printrun/pronsole.py:1759 +#, fuzzy +msgid "Printer is not online. Unable to move." +msgstr "La stampante non è connessa." + +#: printrun/pronsole.py:1649 +msgid "Unknown axis." +msgstr "" + +#: printrun/pronsole.py:1654 +msgid "Invalid distance" +msgstr "" + +#: printrun/pronsole.py:1665 +msgid "Move an axis. Specify the name of the axis and the amount. " +msgstr "" + +#: printrun/pronsole.py:1666 +msgid "" +"move X 10 will move the X axis forward by 10mm at %s mm/min (default XY " +"speed)" +msgstr "" + +#: printrun/pronsole.py:1667 +msgid "move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min" +msgstr "" + +#: printrun/pronsole.py:1668 +msgid "" +"move Z -1 will move the Z axis down by 1mm at %s mm/min (default Z speed)" +msgstr "" + +#: printrun/pronsole.py:1669 +msgid "Common amounts are in the tabcomplete list." +msgstr "" + +#: printrun/pronsole.py:1699 printrun/pronsole.py:1742 +#, fuzzy +msgid "Invalid length given." +msgstr "Periodo non valido." + +#: printrun/pronsole.py:1704 printrun/pronsole.py:1747 +#, fuzzy +msgid "Invalid speed given." +msgstr "Periodo non valido." + +#: printrun/pronsole.py:1712 +msgid "Extruding %fmm of filament." +msgstr "" + +#: printrun/pronsole.py:1714 +msgid "Reversing %fmm of filament." +msgstr "" + +#: printrun/pronsole.py:1716 +msgid "Length is 0, not doing anything." +msgstr "" + +#: printrun/pronsole.py:1722 +msgid "" +"Extrudes a length of filament, 5mm by default, or the number of mm given as " +"a parameter" +msgstr "" + +#: printrun/pronsole.py:1723 +msgid "extrude - extrudes 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1724 +msgid "extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1725 +msgid "extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1726 +msgid "extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1732 +#, fuzzy +msgid "Printer is not online. Unable to reverse." +msgstr "La stampante non è connessa." + +#: printrun/pronsole.py:1751 +msgid "" +"Reverses the extruder, 5mm by default, or the number of mm given as a " +"parameter" +msgstr "" + +#: printrun/pronsole.py:1752 +msgid "reverse - reverses 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1753 +msgid "reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1754 +msgid "reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1755 +msgid "reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1777 +#, fuzzy +msgid "Homes the printer" +msgstr "Connetti alla stampante" + +#: printrun/pronsole.py:1778 +msgid "home - homes all axes and zeroes the extruder(Using G28 and G92)" +msgstr "" + +#: printrun/pronsole.py:1779 +msgid "home xy - homes x and y axes (Using G28)" +msgstr "" + +#: printrun/pronsole.py:1780 +msgid "home z - homes z axis only (Using G28)" +msgstr "" + +#: printrun/pronsole.py:1781 +msgid "home e - set extruder position to zero (Using G92)" +msgstr "" + +#: printrun/pronsole.py:1782 +msgid "home xyze - homes all axes and zeroes the extruder (Using G28 and G92)" +msgstr "" + +#: printrun/pronsole.py:1790 +#, fuzzy +msgid "; Motors off" +msgstr "Spegni motori" + +#: printrun/pronsole.py:1792 +#, fuzzy +msgid "; Extruder off" +msgstr "Estrudi" + +#: printrun/pronsole.py:1794 +msgid "; Heatbed off" +msgstr "" + +#: printrun/pronsole.py:1796 +msgid "; Fan off" +msgstr "" + +#: printrun/pronsole.py:1798 +msgid "; Power supply off" +msgstr "" + +#: printrun/pronsole.py:1801 +#, fuzzy +msgid "Printer is not online. Unable to turn it off." +msgstr "La stampante non è connessa." + +#: printrun/pronsole.py:1804 +msgid "Turns off everything on the printer" +msgstr "" + +#: printrun/pronsole.py:1815 +msgid "G-Code calling host command \"%s\"" +msgstr "" + +#: printrun/pronsole.py:1824 +msgid "" +"Runs a custom script. Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronsole.py:1832 +msgid "" +"Runs a custom script which output gcode which will in turn be executed. " +"Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronterface.py:149 +msgid "Motors off" +msgstr "Spegni motori" + +#: printrun/pronterface.py:149 +msgid "Switch all motors off" +msgstr "" + +#: printrun/pronterface.py:150 +msgid "Advance extruder by set length" +msgstr "" + +#: printrun/pronterface.py:150 +msgid "Extrude" +msgstr "Estrudi" + +#: printrun/pronterface.py:151 +msgid "Reverse" +msgstr "Ritrai" + +#: printrun/pronterface.py:151 +msgid "Reverse extruder by set length" +msgstr "" + +#: printrun/pronterface.py:165 +#, fuzzy +msgid "Pronterface" +msgstr "Interfaccia di stampa" + +#: printrun/pronterface.py:192 +msgid "" +"# I moved all your custom buttons into .pronsolerc.\n" +"# Please don't add them here any more.\n" +"# Backup of your old buttons is in custombtn.old\n" +msgstr "" +"# Ho spostato tutti i tuoi pulsanti personalizzati in .pronsolerc.\n" +"# Per favore non aggiungerne altri qui.\n" +"# Un backup dei tuoi vecchi pulsanti è in custombtn.old\n" + +#: printrun/pronterface.py:197 +msgid "" +"Note!!! You have specified custom buttons in both custombtn.txt and ." +"pronsolerc" +msgstr "" +"Nota!!! Hai specificato pulsanti personalizzati sia in custombtn.txt che in ." +"pronsolerc" + +#: printrun/pronterface.py:198 +msgid "" +"Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt" +msgstr "" +"Ignoro custombtn.txt. Elimina tutti i pulsanti attuali per tornare a " +"custombtn.txt" + +#: printrun/pronterface.py:331 +msgid "Monitoring printer." +msgstr "Sto controllando la stampante." + +#: printrun/pronterface.py:354 +msgid "Setting hotend temperature to %f degrees Celsius." +msgstr "Imposto la temperatura dell'estrusore a %f gradi Celsius." + +#: printrun/pronterface.py:361 printrun/pronterface.py:381 +msgid "You must enter a temperature. (%s)" +msgstr "Devi inserire una temperatura. (%s)" + +#: printrun/pronterface.py:374 +msgid "Setting bed temperature to %f degrees Celsius." +msgstr "Imposto la temperatura del piano di stampa a %f gradi Celsius." + +#: printrun/pronterface.py:392 +msgid "Setting print speed factor to %d%%." +msgstr "" + +#: printrun/pronterface.py:396 +#, fuzzy +msgid "You must enter a speed. (%s)" +msgstr "Devi inserire una temperatura. (%s)" + +#: printrun/pronterface.py:470 +msgid "Plate function activated" +msgstr "" + +#: printrun/pronterface.py:479 +msgid "G-Code plate function activated" +msgstr "" + +#: printrun/pronterface.py:486 +msgid "Plated %s" +msgstr "" + +#: printrun/pronterface.py:500 +msgid "SD Upload" +msgstr "Carica SD" + +#: printrun/pronterface.py:504 +msgid "SD Print" +msgstr "Stampa SD" + +#: printrun/pronterface.py:565 +msgid "" +"Manual move outside of the build volume prevented (see the \"Clamp manual " +"moves\" option)." +msgstr "" + +#: printrun/pronterface.py:621 +msgid "" +"Attempted to write invalid text to console, which could be due to an invalid " +"baudrate" +msgstr "" + +#: printrun/pronterface.py:655 +msgid " Opens file" +msgstr " Apre un file" + +#: printrun/pronterface.py:655 +msgid "&Open..." +msgstr "&Apri..." + +#: printrun/pronterface.py:663 +msgid " Clear output console" +msgstr " Svuota la console" + +#: printrun/pronterface.py:663 +msgid "Clear console" +msgstr "Pulisci console" + +#: printrun/pronterface.py:664 +msgid " Closes the Window" +msgstr " Chiude la finestra" + +#: printrun/pronterface.py:664 +msgid "E&xit" +msgstr "&Esci" + +#: printrun/pronterface.py:665 +msgid "&File" +msgstr "&File" + +#: printrun/pronterface.py:668 +msgid " Edit open file" +msgstr " Modifica file aperto" + +#: printrun/pronterface.py:668 +msgid "&Edit..." +msgstr "&Modifica" + +#: printrun/pronterface.py:669 +msgid " Compose 3D models into a single plate" +msgstr "" + +#: printrun/pronterface.py:669 +#, fuzzy +msgid "Plater" +msgstr "Estrusore:" + +#: printrun/pronterface.py:670 +msgid " Compose G-Codes into a single plate" +msgstr "" + +#: printrun/pronterface.py:670 +msgid "G-Code Plater" +msgstr "" + +#: printrun/pronterface.py:671 +msgid " Exclude parts of the bed from being printed" +msgstr "" + +#: printrun/pronterface.py:671 +msgid "Excluder" +msgstr "" + +#: printrun/pronterface.py:672 +msgid " Project slices" +msgstr " Proietta i layer" + +#: printrun/pronterface.py:672 +msgid "Projector" +msgstr "Proiettore" + +#: printrun/pronterface.py:673 +msgid "&Tools" +msgstr "" + +#: printrun/pronterface.py:676 +msgid "" +" Recover previous print after a disconnect (homes X, Y, restores Z and E " +"status)" +msgstr "" + +#: printrun/pronterface.py:676 +msgid "Recover" +msgstr "" + +#: printrun/pronterface.py:679 +msgid "&Advanced" +msgstr "" + +#: printrun/pronterface.py:686 +#, fuzzy +msgid "Print &settings" +msgstr "Modifica impostazioni" + +#: printrun/pronterface.py:687 +#, fuzzy +msgid "&Filament" +msgstr "&File" + +#: printrun/pronterface.py:688 +#, fuzzy +msgid "&Printer" +msgstr "Stampa" + +#: printrun/pronterface.py:694 +msgid "&Slic3r" +msgstr "" + +#: printrun/pronterface.py:696 +msgid "Failed to load Slic3r configuration:" +msgstr "" + +#: printrun/pronterface.py:702 +msgid "&Macros" +msgstr "&Macro" + +#: printrun/pronterface.py:703 +msgid "<&New...>" +msgstr "<&Nuovo...>" + +#: printrun/pronterface.py:704 +msgid " Options dialog" +msgstr " Finestra di opzioni" + +#: printrun/pronterface.py:704 +msgid "&Options" +msgstr "&Opzioni" + +#: printrun/pronterface.py:706 +msgid " Adjust slicing settings" +msgstr " Configura la generazione del percorso" + +#: printrun/pronterface.py:706 +#, fuzzy +msgid "Slicing settings" +msgstr "Impostazioni di generazione del percorso" + +#: printrun/pronterface.py:708 +msgid "Debug communications" +msgstr "" + +#: printrun/pronterface.py:709 +msgid "Print all G-code sent to and received from the printer." +msgstr "" + +#: printrun/pronterface.py:713 +msgid "&Settings" +msgstr "&Impostazioni" + +#: printrun/pronterface.py:719 +#, fuzzy +msgid "&About Printrun" +msgstr "Controllo automatico temperatura" + +#: printrun/pronterface.py:719 +msgid "Show about dialog" +msgstr "" + +#: printrun/pronterface.py:720 +msgid "&Help" +msgstr "" + +#: printrun/pronterface.py:746 +msgid "" +"Printrun is a pure Python 3D printing (and other types of CNC) host software." +msgstr "" + +#: printrun/pronterface.py:750 +#, fuzzy +msgid "%.02fmm of filament have been extruded during prints" +msgstr "mm di filamento usato in questa stampa\n" + +#: printrun/pronterface.py:781 +#, fuzzy +msgid "Monitor printer status" +msgstr "Controllo automatico temperatura" + +#: printrun/pronterface.py:781 +msgid "" +"Regularly monitor printer temperatures (required to have functional " +"temperature graph or gauges)" +msgstr "" + +#: printrun/pronterface.py:782 +msgid "Path to the simarrange binary to use in the STL plater" +msgstr "" + +#: printrun/pronterface.py:782 +msgid "Simarrange command" +msgstr "" + +#: printrun/pronterface.py:783 +msgid "Circular build platform" +msgstr "" + +#: printrun/pronterface.py:783 +msgid "Draw a circular (or oval) build platform instead of a rectangular one" +msgstr "" + +#: printrun/pronterface.py:784 +#, fuzzy +msgid "Extruders count" +msgstr "Estrudi" + +#: printrun/pronterface.py:784 +msgid "Number of extruders" +msgstr "" + +#: printrun/pronterface.py:785 +msgid "Clamp manual moves" +msgstr "" + +#: printrun/pronterface.py:785 +msgid "Prevent manual moves from leaving the specified build dimensions" +msgstr "" + +#: printrun/pronterface.py:786 +#, fuzzy +msgid "Interface mode" +msgstr "Inserisci il nome della macro" + +#: printrun/pronterface.py:786 +msgid "" +"Standard interface is a one-page, three columns layout with controls/" +"visualization/log\n" +"Compact mode is a one-page, two columns layout with controls + log/" +"visualization\n" +"Tabbed mode is a two-pages mode, where the first page shows controls and the " +"second one shows visualization and log." +msgstr "" + +#: printrun/pronterface.py:787 +msgid "Controls mode" +msgstr "" + +#: printrun/pronterface.py:787 +msgid "" +"Standard controls include all controls needed for printer setup and " +"calibration, while Mini controls are limited to the ones needed for daily " +"printing" +msgstr "" + +#: printrun/pronterface.py:788 +msgid "Add a menu to select Slic3r profiles directly from Pronterface" +msgstr "" + +#: printrun/pronterface.py:788 +msgid "Enable Slic3r integration" +msgstr "" + +#: printrun/pronterface.py:789 +msgid "Update Slic3r default presets" +msgstr "" + +#: printrun/pronterface.py:789 +msgid "" +"When selecting a profile in Slic3r integration menu, also save it as the " +"default Slic3r preset" +msgstr "" + +#: printrun/pronterface.py:790 +msgid "Main visualization" +msgstr "" + +#: printrun/pronterface.py:790 +msgid "Select visualization for main window." +msgstr "" + +#: printrun/pronterface.py:791 +msgid "Use 3D in GCode viewer window" +msgstr "" + +#: printrun/pronterface.py:791 +msgid "Use 3D mode instead of 2D layered mode in the visualization window" +msgstr "" + +#: printrun/pronterface.py:792 +msgid "Use a lighter 3D visualization" +msgstr "" + +#: printrun/pronterface.py:792 +msgid "" +"Use a lighter visualization with simple lines instead of extruded paths for " +"3D viewer" +msgstr "" + +#: printrun/pronterface.py:793 +msgid "Track current layer in main 3D view" +msgstr "" + +#: printrun/pronterface.py:793 +msgid "Track the currently printing layer in the main 3D visualization" +msgstr "" + +#: printrun/pronterface.py:794 +msgid "Display temperature graph" +msgstr "" + +#: printrun/pronterface.py:794 +msgid "Display time-lapse temperature graph" +msgstr "" + +#: printrun/pronterface.py:795 +msgid "Display graphical gauges for temperatures visualization" +msgstr "" + +#: printrun/pronterface.py:795 +msgid "Display temperature gauges" +msgstr "" + +#: printrun/pronterface.py:796 +msgid "Display a checkbox that, when check, locks most of Pronterface" +msgstr "" + +#: printrun/pronterface.py:796 +msgid "Display interface lock checkbox" +msgstr "" + +#: printrun/pronterface.py:797 +msgid "If lock checkbox is enabled, lock the interface when starting a print" +msgstr "" + +#: printrun/pronterface.py:797 +msgid "Lock interface upon print start" +msgstr "" + +#: printrun/pronterface.py:804 +msgid "Preview extrusion width" +msgstr "" + +#: printrun/pronterface.py:804 +#, fuzzy +msgid "Width of Extrusion in Preview" +msgstr "Larghezza dell'estrusione nell'anteprima (default: 0.5)" + +#: printrun/pronterface.py:805 +#, fuzzy +msgid "Fine Grid Spacing" +msgstr "Spaziatura fine della griglia (default: 10)" + +#: printrun/pronterface.py:805 +#, fuzzy +msgid "Fine grid spacing" +msgstr "Spaziatura fine della griglia (default: 10)" + +#: printrun/pronterface.py:806 +#, fuzzy +msgid "Coarse Grid Spacing" +msgstr "Spaziatura larga della griglia (default: 50)" + +#: printrun/pronterface.py:806 +#, fuzzy +msgid "Coarse grid spacing" +msgstr "Spaziatura larga della griglia (default: 50)" + +#: printrun/pronterface.py:807 +msgid "Background color" +msgstr "" + +#: printrun/pronterface.py:807 +#, fuzzy +msgid "Pronterface background color" +msgstr "Colore di sfondo di Pronterface (default: #FFFFFF)" + +#: printrun/pronterface.py:808 +msgid "3D view background color" +msgstr "" + +#: printrun/pronterface.py:808 +msgid "Color of the 3D view background" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "3D view travel moves color" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "Color of travel moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "3D view print moves color" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "Color of print moves with tool 0 in 3D view" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "3D view tool 1 moves color" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "Color of print moves with tool 1 in 3D view" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "3D view printed moves color" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "Color of printed moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "3D view current layer moves color" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "Color of moves in current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "3D view printed current layer moves color" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "Color of already printed moves from current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:815 +msgid "Changing most settings here will require restart to get effect" +msgstr "" + +#: printrun/pronterface.py:815 +msgid "Note:" +msgstr "" + +#: printrun/pronterface.py:822 +msgid "automatically try to connect to printer on startup" +msgstr "" + +#: printrun/pronterface.py:835 printrun/pronterface.py:1316 +msgid "Failed to load recent files list:" +msgstr "" + +#: printrun/pronterface.py:916 +#, fuzzy +msgid "SD upload: %04.2f%% |" +msgstr " stampa da scheda SD:%04.2f %%" + +#: printrun/pronterface.py:917 printrun/pronterface.py:922 +msgid " Line# %d of %d lines |" +msgstr " Linea# %d di %d linee |" + +#: printrun/pronterface.py:919 +#, fuzzy +msgid "SD printing: %04.2f%% |" +msgstr " stampa da scheda SD:%04.2f %%" + +#: printrun/pronterface.py:921 +#, fuzzy +msgid "Printing: %04.2f%% |" +msgstr " Stampa in corso:%04.2f %% |" + +#: printrun/pronterface.py:924 +msgid " Est: %s of %s remaining | " +msgstr " Stima: %s di %s rimanente | " + +#: printrun/pronterface.py:926 +#, fuzzy +msgid " Z: %.3f mm" +msgstr " Z: %0.2f mm" + +#: printrun/pronterface.py:931 +msgid "Disconnecting after 4 failed writes." +msgstr "" + +#: printrun/pronterface.py:972 +#, fuzzy +msgid "Locking interface." +msgstr "Interfaccia di stampa" + +#: printrun/pronterface.py:976 +#, fuzzy +msgid "Unlocking interface." +msgstr "Sto controllando la stampante." + +#: printrun/pronterface.py:985 +msgid "Connecting..." +msgstr "Connessione..." + +#: printrun/pronterface.py:997 +msgid "Could not parse baud rate: " +msgstr "" + +#: printrun/pronterface.py:1013 printrun/pronterface.py:1023 +msgid "Error: You are trying to connect to a non-existing port." +msgstr "" + +#: printrun/pronterface.py:1015 +msgid "Error: You don't have permission to open %s." +msgstr "" + +#: printrun/pronterface.py:1016 +msgid "You might need to add yourself to the dialout group." +msgstr "" + +#: printrun/pronterface.py:1043 +msgid "Disconnected." +msgstr "Disconnesso." + +#: printrun/pronterface.py:1071 +msgid "Reset." +msgstr "Reset." + +#: printrun/pronterface.py:1072 +msgid "Are you sure you want to reset the printer?" +msgstr "Sei sicuro di voler resettare la stampante?" + +#: printrun/pronterface.py:1072 +msgid "Reset?" +msgstr "Reset?" + +#: printrun/pronterface.py:1093 +msgid "Restart" +msgstr "Ricomincia" + +#: printrun/pronterface.py:1126 +msgid "Pick SD filename" +msgstr "Scegli un file dalla scheda SD" + +#: printrun/pronterface.py:1144 +msgid "File upload complete" +msgstr "Caricamento file completato" + +#: printrun/pronterface.py:1151 +msgid "Print paused at: %s" +msgstr "" + +#: printrun/pronterface.py:1163 +msgid "Resume" +msgstr "Ripristina" + +#: printrun/pronterface.py:1166 +#, fuzzy +msgid "Resuming." +msgstr "Ripristina" + +#: printrun/pronterface.py:1192 +msgid "Pick SD file" +msgstr "Scegli un file dalla scheda SD" + +#: printrun/pronterface.py:1192 +msgid "Select the file to print" +msgstr "Seleziona il file da stampare" + +#: printrun/pronterface.py:1228 printrun/pronterface.py:1259 +msgid "Slicing " +msgstr "Generazione del percorso " + +#: printrun/pronterface.py:1237 +msgid "Failed to execute slicing software: " +msgstr "Imposibile eseguire il software di generazione percorso: " + +#: printrun/pronterface.py:1244 +msgid "Slicing..." +msgstr "Generazione percorso..." + +#: printrun/pronterface.py:1296 +msgid "Open file to print" +msgstr "Apri il file da stampare" + +#: printrun/pronterface.py:1297 +#, fuzzy +msgid "" +"OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*." +"gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*" +msgstr "files OBJ, STL e GCODE (;*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ;)" + +#: printrun/pronterface.py:1325 +msgid "Could not update recent files list:" +msgstr "" + +#: printrun/pronterface.py:1359 +msgid "Loaded %s, %d lines" +msgstr "Caricato %s, %d linee" + +#: printrun/pronterface.py:1362 +msgid "Load File" +msgstr "Apri file" + +#: printrun/pronterface.py:1376 +#, fuzzy +msgid "%.2fmm of filament used in this print" +msgstr "mm di filamento usato in questa stampa\n" + +#: printrun/pronterface.py:1377 +msgid "The print goes:" +msgstr "" + +#: printrun/pronterface.py:1378 +#, fuzzy +msgid "- from %.2f mm to %.2f mm in X and is %.2f mm wide" +msgstr "" +"la stampa va da %f mm a %f mm in X\n" +"ed è %f mm di larghezza\n" + +#: printrun/pronterface.py:1379 +#, fuzzy +msgid "- from %.2f mm to %.2f mm in Y and is %.2f mm deep" +msgstr "" +"la stampa va da %f mm a %f mm in Y\n" +"ed è %f mm di larghezza\n" + +#: printrun/pronterface.py:1380 +#, fuzzy +msgid "- from %.2f mm to %.2f mm in Z and is %.2f mm high" +msgstr "" +"la stampa va da %f mm a %f mm in Z\n" +"ed è %f mm di altezza\n" + +#: printrun/pronterface.py:1450 +msgid "Printer is now online." +msgstr "La stampante ora è connessa." + +#: printrun/pronterface.py:1455 +msgid "Disconnect" +msgstr "Disconnettere." + +#: printrun/pronterface.py:1688 +msgid "click to add new custom button" +msgstr "clicca per aggiungere un nuovo pulsante personalizzato" + +#: printrun/pronterface.py:1694 +msgid "Execute command: " +msgstr "Esegui comando: " + +#: printrun/pronterface.py:1714 +msgid "" +"Defines custom button. Usage: button \"title\" [/c \"colour\"] command" +msgstr "" +"Definisce un pulsante personalizzato. Uso: button \"titolo\" [/c " +"\"colore\"] comando" + +#: printrun/pronterface.py:1736 +msgid "Custom button number should be between 0 and 63" +msgstr "Il numero del pulsante personalizzato dev'essere tra 0 e 63" + +#: printrun/pronterface.py:1827 +msgid "Edit custom button '%s'" +msgstr "Modifica pulsante personalizzato '%s'" + +#: printrun/pronterface.py:1829 +msgid "Move left <<" +msgstr "Muovi a sinistra <<" + +#: printrun/pronterface.py:1832 +msgid "Move right >>" +msgstr "Muovi a destra >>" + +#: printrun/pronterface.py:1836 +msgid "Remove custom button '%s'" +msgstr "Elimina pulsante personalizzato '%s'" + +#: printrun/pronterface.py:1839 +msgid "Add custom button" +msgstr "Aggiungi pulsante personalizzato" + +#: printrun/pronterface.py:1972 +msgid "event object missing" +msgstr "oggetto dell'evento mancante" + +#: printrun/pronterface.py:1985 +msgid "Do you want to erase the macro?" +msgstr "Vuoi cancellare la macro?" + +#: printrun/pronterface.py:1989 +msgid "Cancelled." +msgstr "Annullato." + +#: printrun/pronterface.py:2007 +msgid "Enter macro name" +msgstr "Inserisci il nome della macro" + +#: printrun/pronterface.py:2010 +msgid "Macro name:" +msgstr "Nome macro:" + +#: printrun/pronterface.py:2013 +msgid "Ok" +msgstr "Ok" + +#: printrun/pronterface.py:2035 +#, fuzzy +msgid "Macro name may contain only ASCII alphanumeric symbols and underscores" +msgstr "" +"I nomi delle macro possono contenere solo simboli alfanumerici e underscore" + +#: printrun/pronterface.py:2038 +msgid "Name '%s' is being used by built-in command" +msgstr "Nome '%s' è usato da un comando interno" + +#: pronsole.py:31 +msgid "Caught an exception, exiting:" +msgstr "" + +#~ msgid "Folder of last opened file" +#~ msgstr "Cartella dell'ultimo file aperto" + +#~ msgid "Check temp" +#~ msgstr "Leggi temperatura" + +#~ msgid "Mini mode" +#~ msgstr "Contrai" + +#~ msgid "Compose" +#~ msgstr "Componi" + +#~ msgid "Z:" +#~ msgstr "Z:" + +#~ msgid "Full mode" +#~ msgstr "Espandi" + +#~ msgid "Printer is online. " +#~ msgstr "La stampante è online. " + +#~ msgid "Bed" +#~ msgstr "Letto" + +#~ msgid "Hotend" +#~ msgstr "Estrusore" + +#~ msgid ", %d lines" +#~ msgstr ", %d linee" + +#~ msgid "Paused." +#~ msgstr "In pausa." + +#~ msgid "" +#~ "Slice command\n" +#~ " default:\n" +#~ " python skeinforge/skeinforge_application/skeinforge_utilities/" +#~ "skeinforge_craft.py $s)" +#~ msgstr "" +#~ "Comando del generatore di percorso\n" +#~ " default:\n" +#~ " python skeinforge/skeinforge_application/skeinforge_utilities/" +#~ "skeinforge_craft.py $s)" + +#~ msgid "" +#~ "Slice settings command\n" +#~ " default:\n" +#~ " python skeinforge/skeinforge_application/skeinforge.py" +#~ msgstr "" +#~ "Comando di configurazione del generatore di percorso\n" +#~ " default:\n" +#~ " python skeinforge/skeinforge_application/skeinforge.py" diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/nl/LC_MESSAGES/pronterface.mo Binary file printrun-src/locale/nl/LC_MESSAGES/pronterface.mo has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/nl/LC_MESSAGES/pronterface.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/nl/LC_MESSAGES/pronterface.po Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,2254 @@ +# Pronterface Message Catalog Dutch +# Copyright (C) 2011 Ruben Lubbes +# Ruben Lubbes , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: Pronterface rl1\n" +"POT-Creation-Date: 2014-03-30 14:35+CEST\n" +"PO-Revision-Date: 2011-09-06 16:31+0100\n" +"Last-Translator: Ruben Lubbes \n" +"Language-Team: NL \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + +#: printrun/excluder.py:26 +msgid "" +"Part excluder: draw rectangles where print instructions should be ignored" +msgstr "" + +#: printrun/excluder.py:27 printrun/excluder.py:29 +msgid "Reset selection" +msgstr "" + +#: printrun/gcodeplater.py:43 printrun/gcodeplater.py:44 +msgid "GCODE files (*.gcode;*.GCODE;*.g)" +msgstr "" + +#: printrun/gcodeplater.py:112 printrun/gcodeplater.py:152 +msgid "Warning: no rotation support for now, object won't be correctly rotated" +msgstr "" + +#: printrun/gcodeplater.py:141 printrun/gcodeplater.py:178 +msgid "Exported merged G-Codes to %s" +msgstr "" + +#: printrun/gcview.py:352 +msgid "Fit to plate" +msgstr "" + +#: printrun/gcview.py:353 +msgid "Fit to plate [F]" +msgstr "" + +#: printrun/gui/__init__.py:19 printrun/pronterface.py:45 +msgid "WX is not installed. This program requires WX to run." +msgstr "WX is niet geïnstalleerd. Dit programma vereist WX." + +#: printrun/gui/__init__.py:159 +#, fuzzy +msgid "Commands" +msgstr "Commando" + +#: printrun/gui/__init__.py:160 +msgid "Status" +msgstr "" + +#: printrun/gui/controls.py:126 +#, fuzzy +msgid "Heat:" +msgstr "Element:" + +#: printrun/gui/controls.py:129 +msgid "Switch Hotend Off" +msgstr "" + +#: printrun/gui/controls.py:129 printrun/gui/controls.py:149 +#: printrun/gui/toolbar.py:85 +msgid "Off" +msgstr "" + +#: printrun/gui/controls.py:137 +msgid "Select Temperature for Hotend" +msgstr "" + +#: printrun/gui/controls.py:141 +msgid "Switch Hotend On" +msgstr "" + +#: printrun/gui/controls.py:141 printrun/gui/controls.py:161 +#: printrun/gui/controls.py:198 +msgid "Set" +msgstr "Stel in" + +#: printrun/gui/controls.py:146 printrun/gui/controls.py:215 +msgid "Bed:" +msgstr "Bed:" + +#: printrun/gui/controls.py:149 +msgid "Switch Heated Bed Off" +msgstr "" + +#: printrun/gui/controls.py:157 +msgid "Select Temperature for Heated Bed" +msgstr "" + +#: printrun/gui/controls.py:161 +msgid "Switch Heated Bed On" +msgstr "" + +#: printrun/gui/controls.py:187 +msgid "Print speed:" +msgstr "" + +#: printrun/gui/controls.py:192 printrun/gui/controls.py:207 +msgid "%d%%" +msgstr "" + +#: printrun/gui/controls.py:198 +msgid "Set print speed factor" +msgstr "" + +#: printrun/gui/controls.py:213 +msgid "Heater:" +msgstr "Element:" + +#: printrun/gui/controls.py:265 +msgid "Length:" +msgstr "" + +#: printrun/gui/controls.py:267 +msgid "mm" +msgstr "mm" + +#: printrun/gui/controls.py:267 +msgid "mm @" +msgstr "" + +#: printrun/gui/controls.py:269 +msgid "Amount to Extrude or Retract (mm)" +msgstr "" + +#: printrun/gui/controls.py:272 +msgid "Extrude / Retract speed (mm/min)" +msgstr "" + +#: printrun/gui/controls.py:278 +msgid "Speed:" +msgstr "" + +#: printrun/gui/controls.py:279 +msgid "" +"mm/\n" +"min" +msgstr "" + +#: printrun/gui/controls.py:290 +msgid "Tool:" +msgstr "" + +#: printrun/gui/controls.py:293 +msgid "Click to switch current extruder" +msgstr "" + +#: printrun/gui/controls.py:310 +msgid "Select current extruder" +msgstr "" + +#: printrun/gui/controls.py:371 +msgid "Set Maximum Speed for X & Y axes (mm/min)" +msgstr "" + +#: printrun/gui/controls.py:372 +msgid "XY:" +msgstr "XY:" + +#: printrun/gui/controls.py:374 +#, fuzzy +msgid "mm/min Z:" +msgstr "mm/min" + +#: printrun/gui/controls.py:376 +msgid "Set Maximum Speed for Z axis (mm/min)" +msgstr "" + +#: printrun/gui/graph.py:28 +msgid "Temperature graph" +msgstr "" + +#: printrun/gui/log.py:32 +msgid "" +"Send commands to printer\n" +"(Type 'help' for simple\n" +"help function)" +msgstr "" + +#: printrun/gui/log.py:39 +msgid "Send" +msgstr "Zend" + +#: printrun/gui/log.py:39 +#, fuzzy +msgid "Send Command to Printer" +msgstr "Printercommunicatie volgen" + +#: printrun/gui/toolbar.py:23 +msgid "Lock" +msgstr "" + +#: printrun/gui/toolbar.py:25 +msgid "Lock graphical interface" +msgstr "" + +#: printrun/gui/toolbar.py:32 +msgid "" +"Communication Settings\n" +"Click to rescan ports" +msgstr "" + +#: printrun/gui/toolbar.py:32 +msgid "Port" +msgstr "Poort" + +#: printrun/gui/toolbar.py:37 +msgid "Select Port Printer is connected to" +msgstr "" + +#: printrun/gui/toolbar.py:46 +msgid "Select Baud rate for printer communication" +msgstr "" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1052 +msgid "Connect" +msgstr "Verbind" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1053 +msgid "Connect to the printer" +msgstr "Verbind met printer" + +#: printrun/gui/toolbar.py:61 +msgid "Reset" +msgstr "Reset" + +#: printrun/gui/toolbar.py:61 +#, fuzzy +msgid "Reset the printer" +msgstr "Verbind met printer" + +#: printrun/gui/toolbar.py:69 +#, fuzzy +msgid "Load a 3D model file" +msgstr "open bestand" + +#: printrun/gui/toolbar.py:69 +msgid "Load file" +msgstr "open bestand" + +#: printrun/gui/toolbar.py:70 +msgid "SD" +msgstr "" + +#: printrun/gui/toolbar.py:70 +#, fuzzy +msgid "SD Card Printing" +msgstr "Afdruk van SD" + +#: printrun/gui/toolbar.py:74 +msgid "Start Printing Loaded File" +msgstr "" + +#: printrun/gui/toolbar.py:74 printrun/pronterface.py:1003 +#: printrun/pronterface.py:1062 printrun/pronterface.py:1078 +#: printrun/pronterface.py:1363 printrun/pronterface.py:1445 +msgid "Print" +msgstr "Printen" + +#: printrun/gui/toolbar.py:80 +msgid "Pause Current Print" +msgstr "" + +#: printrun/gui/toolbar.py:80 printrun/pronterface.py:1002 +#: printrun/pronterface.py:1061 printrun/pronterface.py:1081 +#: printrun/pronterface.py:1091 printrun/pronterface.py:1172 +#: printrun/pronterface.py:1364 +msgid "Pause" +msgstr "Pauze" + +#: printrun/gui/toolbar.py:85 +msgid "Turn printer off" +msgstr "" + +#: printrun/gui/viz.py:65 +msgid "" +"Click to examine / edit\n" +" layers of loaded file" +msgstr "" + +#: printrun/gui/widgets.py:36 +msgid "Find" +msgstr "" + +#: printrun/gui/widgets.py:38 +msgid "Save" +msgstr "" + +#: printrun/gui/widgets.py:43 printrun/objectplater.py:76 +#: printrun/pronterface.py:1257 printrun/pronterface.py:2017 +msgid "Cancel" +msgstr "Annuleer" + +#: printrun/gui/widgets.py:70 +#, fuzzy +msgid "Not Found!" +msgstr "Bestand niet gevonden!" + +#: printrun/gui/widgets.py:124 +#, fuzzy +msgid "Printer settings" +msgstr "Wijzig instellingen" + +#: printrun/gui/widgets.py:125 +#, fuzzy +msgid "User interface" +msgstr "Printer Interface" + +#: printrun/gui/widgets.py:126 +#, fuzzy +msgid "Colors" +msgstr "Kleur" + +#: printrun/gui/widgets.py:127 +msgid "External commands" +msgstr "" + +#: printrun/gui/widgets.py:132 +msgid "Edit settings" +msgstr "Wijzig instellingen" + +#: printrun/gui/widgets.py:135 +#, fuzzy +msgid "Settings" +msgstr "&Instellingen" + +#: printrun/gui/widgets.py:187 +msgid "Custom button" +msgstr "Gedefinieerde knop" + +#: printrun/gui/widgets.py:193 +msgid "Button title" +msgstr "Knoptitel" + +#: printrun/gui/widgets.py:196 +msgid "Command" +msgstr "Commando" + +#: printrun/gui/widgets.py:205 +msgid "Color" +msgstr "Kleur" + +#: printrun/gviz.py:29 +msgid "Layer number and Z position show here when you scroll" +msgstr "" + +#: printrun/gviz.py:39 +msgid "Zoom In [+]" +msgstr "" + +#: printrun/gviz.py:40 +msgid "Zoom Out [-]" +msgstr "" + +#: printrun/gviz.py:42 +msgid "Move Up a Layer [U]" +msgstr "" + +#: printrun/gviz.py:43 +msgid "Move Down a Layer [D]" +msgstr "" + +#: printrun/gviz.py:44 +#, fuzzy +msgid "Reset view" +msgstr "Reset" + +#: printrun/gviz.py:68 +msgid "Gcode view, shift to move view, mousewheel to set layer" +msgstr "" + +#: printrun/gviz.py:105 printrun/gviz.py:241 +msgid "Layer %d - Going Up - Z = %.03f mm" +msgstr "" + +#: printrun/gviz.py:250 +msgid "Layer %d - Going Down - Z = %.03f mm" +msgstr "" + +#: printrun/objectplater.py:35 +msgid "Plate building tool" +msgstr "" + +#: printrun/objectplater.py:45 +msgid "Clear" +msgstr "" + +#: printrun/objectplater.py:49 +#, fuzzy +msgid "Load" +msgstr "Geladen " + +#: printrun/objectplater.py:53 +msgid "Snap to Z = 0" +msgstr "" + +#: printrun/objectplater.py:57 +msgid "Put at center" +msgstr "" + +#: printrun/objectplater.py:61 +msgid "Delete" +msgstr "" + +#: printrun/objectplater.py:65 +msgid "Auto arrange" +msgstr "" + +#: printrun/objectplater.py:69 +msgid "Export" +msgstr "" + +#: printrun/objectplater.py:73 +msgid "Done" +msgstr "" + +#: printrun/objectplater.py:151 +msgid "Autoplating" +msgstr "" + +#: printrun/objectplater.py:199 +msgid "Bed full, sorry sir :(" +msgstr "" + +#: printrun/objectplater.py:209 +msgid "" +"Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "" + +#: printrun/objectplater.py:210 +#, fuzzy +msgid "Clear the grid?" +msgstr " Sluit het venster" + +#: printrun/objectplater.py:256 +#, fuzzy +msgid "Pick file to load" +msgstr "Kies bestand op SD" + +#: printrun/objectplater.py:267 +#, fuzzy +msgid "Pick file to save to" +msgstr "Kies bestandsnaam op SD" + +#: printrun/plater.py:212 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL|OpenSCAD files (*.scad)|*.scad" +msgstr "" + +#: printrun/plater.py:213 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL" +msgstr "" + +#: printrun/plater.py:244 +#, fuzzy +msgid "Loading STL file failed" +msgstr "Bestand openen mislukt" + +#: printrun/plater.py:244 printrun/plater.py:251 +msgid "Error" +msgstr "" + +#: printrun/plater.py:251 +#, fuzzy +msgid "Loading OpenSCAD file failed" +msgstr "Bestand openen mislukt" + +#: printrun/plater.py:284 +msgid "Couldn't load non-existing file %s" +msgstr "" + +#: printrun/plater.py:360 +msgid "Wrote plate to %s" +msgstr "" + +#: printrun/plater.py:367 +msgid "" +"Failed to use simarrange for plating, falling back to the standard method" +msgstr "" + +#: printrun/plater.py:372 +msgid "Autoplating using simarrange" +msgstr "" + +#: printrun/plater.py:387 +msgid "Plate full, please remove some objects" +msgstr "" + +#: printrun/plater.py:404 +msgid "simarrange failed" +msgstr "" + +#: printrun/printcore.py:177 +msgid "Could not connect to %s:%s:" +msgstr "" + +#: printrun/printcore.py:178 +msgid "Socket error %s:" +msgstr "" + +#: printrun/printcore.py:191 printrun/printcore.py:196 +msgid "Could not connect to %s at baudrate %s:" +msgstr "" + +#: printrun/printcore.py:192 +msgid "Serial error: %s" +msgstr "" + +#: printrun/printcore.py:197 +msgid "IO error: %s" +msgstr "" + +#: printrun/printcore.py:231 +msgid "Can't read from printer (disconnected?) (SelectError {0}): {1}" +msgstr "" + +#: printrun/printcore.py:234 +msgid "SelectError ({0}): {1}" +msgstr "" + +#: printrun/printcore.py:237 +msgid "Can't read from printer (disconnected?) (SerialException): {0}" +msgstr "" + +#: printrun/printcore.py:240 +msgid "Can't read from printer (disconnected?) (Socket error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:245 +msgid "Can't read from printer (disconnected?) (OS Error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:259 +msgid "Aborting connection attempt after 4 failed writes." +msgstr "" + +#: printrun/printcore.py:462 printrun/printcore.py:470 +#: printrun/pronsole.py:1192 printrun/pronsole.py:1250 +#: printrun/pronterface.py:209 printrun/pronterface.py:962 +#: printrun/pronterface.py:1111 printrun/pronterface.py:1178 +msgid "Not connected to printer." +msgstr "Printer is niet verbonden." + +#: printrun/printcore.py:479 +msgid "Print start callback failed with:" +msgstr "" + +#: printrun/printcore.py:490 +msgid "Print end callback failed with:" +msgstr "" + +#: printrun/printcore.py:493 +msgid "Print thread died due to the following error:" +msgstr "" + +#: printrun/printcore.py:584 +msgid "Could not analyze command %s:" +msgstr "" + +#: printrun/printcore.py:601 +msgid "Can't write to printer (disconnected ?):" +msgstr "" + +#: printrun/printcore.py:604 +msgid "Can't write to printer (disconnected?) (Socket error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:607 +msgid "Can't write to printer (disconnected?) (SerialException): {0}" +msgstr "" + +#: printrun/printcore.py:610 +msgid "Socket connection broken, disconnected. ({0}): {1}" +msgstr "" + +#: printrun/pronsole.py:72 +#, fuzzy +msgid "Default: " +msgstr "Standaardinstelling" + +#: printrun/pronsole.py:73 +msgid "(Control-doubleclick to reset to default value)" +msgstr "" + +#: printrun/pronsole.py:108 +#, fuzzy +msgid "" +"Are you sure you want to reset the setting to the default value: {0!r} ?" +msgstr "Weet je zeker dat je de printer wilt resetten?" + +#: printrun/pronsole.py:108 +msgid "Confirm set default" +msgstr "" + +#: printrun/pronsole.py:258 +msgid "Width" +msgstr "" + +#: printrun/pronsole.py:261 +msgid "Depth" +msgstr "" + +#: printrun/pronsole.py:264 +msgid "Height" +msgstr "" + +#: printrun/pronsole.py:267 +msgid "X offset" +msgstr "" + +#: printrun/pronsole.py:270 +msgid "Y offset" +msgstr "" + +#: printrun/pronsole.py:273 +msgid "Z offset" +msgstr "" + +#: printrun/pronsole.py:276 +msgid "X home pos." +msgstr "" + +#: printrun/pronsole.py:279 +msgid "Y home pos." +msgstr "" + +#: printrun/pronsole.py:282 +msgid "Z home pos." +msgstr "" + +#: printrun/pronsole.py:301 +#, fuzzy +msgid "Port used to communicate with printer" +msgstr "Printer is niet verbonden." + +#: printrun/pronsole.py:301 +msgid "Serial port" +msgstr "" + +#: printrun/pronsole.py:302 +msgid "Baud rate" +msgstr "" + +#: printrun/pronsole.py:302 +msgid "Communications Speed" +msgstr "" + +#: printrun/pronsole.py:303 +msgid "TCP streaming mode" +msgstr "" + +#: printrun/pronsole.py:303 +msgid "" +"When using a TCP connection to the printer, the streaming mode will not wait " +"for acks from the printer to send new commands. This will break things such " +"as ETA prediction, but can result in smoother prints." +msgstr "" + +#: printrun/pronsole.py:304 +msgid "Bed temperature for ABS" +msgstr "" + +#: printrun/pronsole.py:304 +msgid "Heated Build Platform temp for ABS (deg C)" +msgstr "" + +#: printrun/pronsole.py:305 +msgid "Bed temperature for PLA" +msgstr "" + +#: printrun/pronsole.py:305 +msgid "Heated Build Platform temp for PLA (deg C)" +msgstr "" + +#: printrun/pronsole.py:306 +msgid "Extruder temp for ABS (deg C)" +msgstr "" + +#: printrun/pronsole.py:306 +msgid "Extruder temperature for ABS" +msgstr "" + +#: printrun/pronsole.py:307 +msgid "Extruder temp for PLA (deg C)" +msgstr "" + +#: printrun/pronsole.py:307 +msgid "Extruder temperature for PLA" +msgstr "" + +#: printrun/pronsole.py:308 +msgid "Feedrate for Control Panel Moves in X and Y (mm/min)" +msgstr "" + +#: printrun/pronsole.py:308 +msgid "X && Y manual feedrate" +msgstr "" + +#: printrun/pronsole.py:309 +msgid "Feedrate for Control Panel Moves in Z (mm/min)" +msgstr "" + +#: printrun/pronsole.py:309 +msgid "Z manual feedrate" +msgstr "" + +#: printrun/pronsole.py:310 +msgid "E manual feedrate" +msgstr "" + +#: printrun/pronsole.py:310 +msgid "Feedrate for Control Panel Moves in Extrusions (mm/min)" +msgstr "" + +#: printrun/pronsole.py:311 +#, fuzzy +msgid "Slice command" +msgstr "Commando" + +#: printrun/pronsole.py:312 +#, fuzzy +msgid "Slice settings command" +msgstr "SFACT Instellingen" + +#: printrun/pronsole.py:312 +msgid "Slicer options command" +msgstr "" + +#: printrun/pronsole.py:313 +msgid "Executable to run when the print is finished" +msgstr "" + +#: printrun/pronsole.py:313 +#, fuzzy +msgid "Final command" +msgstr "Commando" + +#: printrun/pronsole.py:314 +#, fuzzy +msgid "Error command" +msgstr "Commando" + +#: printrun/pronsole.py:314 +msgid "Executable to run when an error occurs" +msgstr "" + +#: printrun/pronsole.py:381 +msgid "Failed to run callback after setting \"%s\":" +msgstr "" + +#: printrun/pronsole.py:469 +msgid "Build dimensions" +msgstr "" + +#: printrun/pronsole.py:469 +msgid "" +"Dimensions of Build Platform\n" +" & optional offset of origin\n" +" & optional switch position\n" +"\n" +"Examples:\n" +" XXXxYYY\n" +" XXX,YYY,ZZZ\n" +" XXXxYYYxZZZ+OffX+OffY+OffZ\n" +"XXXxYYYxZZZ+OffX+OffY+OffZ+HomeX+HomeY+HomeZ" +msgstr "" + +#: printrun/pronsole.py:495 +msgid "" +"Welcome to the printer console! Type \"help\" for a list of available " +"commands." +msgstr "" + +#: printrun/pronsole.py:657 printrun/pronsole.py:665 printrun/pronsole.py:673 +#: printrun/pronsole.py:1519 printrun/pronsole.py:1546 +#: printrun/pronsole.py:1618 printrun/pronterface.py:357 +#: printrun/pronterface.py:377 printrun/pronterface.py:394 +msgid "Printer is not online." +msgstr "Printer is niet verbonden." + +#: printrun/pronsole.py:692 +msgid "Exiting program. Goodbye!" +msgstr "" + +#: printrun/pronsole.py:697 +msgid "Disconnects from the printer and exits the program." +msgstr "" + +#: printrun/pronsole.py:974 +msgid "" +"load this file on startup instead of .pronsolerc ; you may chain config " +"files, if so settings auto-save will use the last specified file" +msgstr "" + +#: printrun/pronsole.py:975 +msgid "" +"executes command after configuration/.pronsolerc is loaded ; macros/settings " +"from these commands are not autosaved" +msgstr "" + +#: printrun/pronsole.py:976 +msgid "file to load" +msgstr "" + +#: printrun/pronsole.py:1104 +#, fuzzy +msgid "Loaded %s, %d lines." +msgstr "Geladen %s, %d regels" + +#: printrun/pronsole.py:1105 printrun/pronterface.py:1381 +msgid "Estimated duration: %d layers, %s" +msgstr "" + +#: printrun/pronsole.py:1134 +msgid "No file name given." +msgstr "" + +#: printrun/pronsole.py:1140 +msgid "Skeining file: %s" +msgstr "" + +#: printrun/pronsole.py:1142 printrun/pronterface.py:1305 +msgid "File not found!" +msgstr "Bestand niet gevonden!" + +#: printrun/pronsole.py:1147 +msgid "Entering slicer settings: %s" +msgstr "" + +#: printrun/pronsole.py:1151 +#, fuzzy +msgid "Slicing: " +msgstr "Skeinforge draait" + +#: printrun/pronsole.py:1158 +#, fuzzy +msgid "Loading sliced file." +msgstr "open bestand" + +#: printrun/pronsole.py:1161 +#, fuzzy +msgid "Slicing failed: %s" +msgstr "Skeinforge draait" + +#: printrun/pronsole.py:1174 +msgid "" +"Creates a gcode file from an stl model using the slicer (with tab-completion)" +msgstr "" + +#: printrun/pronsole.py:1175 +msgid "slice filename.stl - create gcode file" +msgstr "" + +#: printrun/pronsole.py:1176 +msgid "" +"slice filename.stl view - create gcode file and view using skeiniso (if " +"using skeinforge)" +msgstr "" + +#: printrun/pronsole.py:1177 +#, fuzzy +msgid "slice set - adjust slicer settings" +msgstr "Instellen SFACT" + +#: printrun/pronsole.py:1189 +msgid "Please enter target name in 8.3 format." +msgstr "" + +#: printrun/pronsole.py:1195 +msgid "Uploading as %s" +msgstr "" + +#: printrun/pronsole.py:1196 +msgid "Uploading %s" +msgstr "" + +#: printrun/pronsole.py:1198 +msgid "Press Ctrl-C to interrupt upload." +msgstr "" + +#: printrun/pronsole.py:1201 +msgid "Progress: " +msgstr "" + +#: printrun/pronsole.py:1212 +msgid "Upload completed. %s should now be on the card." +msgstr "" + +#: printrun/pronsole.py:1216 +msgid "...interrupted!" +msgstr "" + +#: printrun/pronsole.py:1218 +msgid "Something wrong happened while uploading:" +msgstr "" + +#: printrun/pronsole.py:1224 +msgid "A partial file named %s may have been written to the sd card." +msgstr "" + +#: printrun/pronsole.py:1241 +msgid "" +"Send a loaded gcode file to the printer. Load a file with the load command " +"first." +msgstr "" + +#: printrun/pronsole.py:1243 +msgid "Send a loaded gcode file to the printer. You have %s loaded right now." +msgstr "" + +#: printrun/pronsole.py:1247 printrun/pronterface.py:730 +#: printrun/pronterface.py:1108 +msgid "No file loaded. Please use load first." +msgstr "Geen bestand geladen. Eerst bestand inladen." + +#: printrun/pronsole.py:1252 +#, fuzzy +msgid "Printing %s" +msgstr " Printen:%04.2f %% |" + +#: printrun/pronsole.py:1253 +msgid "You can monitor the print with the monitor command." +msgstr "" + +#: printrun/pronsole.py:1261 +msgid "Not printing, cannot pause." +msgstr "" + +#: printrun/pronsole.py:1267 +#, fuzzy +msgid "Pauses a running print" +msgstr "Start het printen" + +#: printrun/pronsole.py:1274 +msgid "Not paused, unable to resume. Start a print first." +msgstr "" + +#: printrun/pronsole.py:1284 +msgid "Resumes a paused print." +msgstr "" + +#: printrun/pronsole.py:1293 +msgid "Files on SD card:" +msgstr "" + +#: printrun/pronsole.py:1307 printrun/pronsole.py:1349 +#: printrun/pronsole.py:1562 +#, fuzzy +msgid "Printer is not online. Please connect to it first." +msgstr "Printer is niet verbonden." + +#: printrun/pronsole.py:1312 +msgid "Lists files on the SD card" +msgstr "" + +#: printrun/pronsole.py:1316 printrun/pronterface.py:1645 +msgid "Opening file failed." +msgstr "Bestand openen mislukt" + +#: printrun/pronsole.py:1322 printrun/pronterface.py:1651 +msgid "Starting print" +msgstr "Start het printen" + +#: printrun/pronsole.py:1345 +#, fuzzy +msgid "Resets the printer." +msgstr "Verbind met printer" + +#: printrun/pronsole.py:1355 +#, fuzzy +msgid "File is not present on card. Please upload it first." +msgstr "Geen bestand geladen. Eerst bestand inladen." + +#: printrun/pronsole.py:1359 +msgid "Printing file: %s from SD card." +msgstr "" + +#: printrun/pronsole.py:1360 +msgid "Requesting SD print..." +msgstr "" + +#: printrun/pronsole.py:1364 +msgid "Print a file from the SD card. Tab completes with available file names." +msgstr "" + +#: printrun/pronsole.py:1365 +msgid "sdprint filename.g" +msgstr "" + +#: printrun/pronsole.py:1382 +msgid "Print resumed at: %s" +msgstr "" + +#: printrun/pronsole.py:1384 +msgid "Print started at: %s" +msgstr "" + +#: printrun/pronsole.py:1392 +msgid "Failed to inhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1399 +msgid "Failed to uninhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1403 +msgid "Print ended at: %(end_time)s and took %(duration)s" +msgstr "" + +#: printrun/pronsole.py:1457 +msgid "Printer is not currently printing. No ETA available." +msgstr "" + +#: printrun/pronsole.py:1460 +msgid "Est: %s of %s remaining" +msgstr "" + +#: printrun/pronsole.py:1465 +msgid "Displays estimated remaining print time." +msgstr "" + +#: printrun/pronsole.py:1498 +#, fuzzy +msgid "Read the extruder and bed temperature." +msgstr "Er moet een temperatuur worden ingesteld." + +#: printrun/pronsole.py:1507 printrun/pronsole.py:1540 +msgid "You must enter a temperature." +msgstr "Er moet een temperatuur worden ingesteld." + +#: printrun/pronsole.py:1512 +msgid "" +"%s is a high temperature to set your extruder to. Are you sure you want to " +"do that?" +msgstr "" + +#: printrun/pronsole.py:1517 +#, fuzzy +msgid "Setting hotend temperature to %s degrees Celsius." +msgstr "Stel elementtemperatuur in op %f graden Celsius." + +#: printrun/pronsole.py:1521 printrun/pronterface.py:359 +msgid "" +"You cannot set negative temperatures. To turn the hotend off entirely, set " +"its temperature to 0." +msgstr "" +"Negatieve temperatuur is niet instelbaar. Om het element uit te schakelen " +"wordt temperatuur 0 ingesteld." + +#: printrun/pronsole.py:1524 +#, fuzzy +msgid "Sets the hotend temperature to the value entered." +msgstr "Stel elementtemperatuur in op %f graden Celsius." + +#: printrun/pronsole.py:1525 printrun/pronsole.py:1552 +msgid "Enter either a temperature in celsius or one of the following keywords" +msgstr "" + +#: printrun/pronsole.py:1544 +#, fuzzy +msgid "Setting bed temperature to %s degrees Celsius." +msgstr "Bed teperatuur ingesteld op %f graden Celsius." + +#: printrun/pronsole.py:1548 printrun/pronterface.py:379 +msgid "" +"You cannot set negative temperatures. To turn the bed off entirely, set its " +"temperature to 0." +msgstr "" +"Negatieve temperatuur is niet instelbaar. Om het printbed uit te schakelen " +"wordt temperatuur 0 ingesteld." + +#: printrun/pronsole.py:1551 +#, fuzzy +msgid "Sets the bed temperature to the value entered." +msgstr "Bed teperatuur ingesteld op %f graden Celsius." + +#: printrun/pronsole.py:1565 +msgid "Printer is not printing. Please print something before monitoring." +msgstr "" + +#: printrun/pronsole.py:1567 +#, fuzzy +msgid "Monitoring printer, use ^C to interrupt." +msgstr "Printercommunicatie wordt gevolgd." + +#: printrun/pronsole.py:1572 printrun/pronterface.py:329 +msgid "Invalid period given." +msgstr "Foute gegevens ingevoerd" + +#: printrun/pronsole.py:1573 +msgid "Updating values every %f seconds." +msgstr "" + +#: printrun/pronsole.py:1584 printrun/pronsole.py:1587 +msgid "Print progress: " +msgstr "" + +#: printrun/pronsole.py:1595 printrun/pronterface.py:333 +msgid "Done monitoring." +msgstr "Klaar met volgen." + +#: printrun/pronsole.py:1599 +msgid "Monitor a machine's temperatures and an SD print's status." +msgstr "" + +#: printrun/pronsole.py:1600 +msgid "" +"monitor - Reports temperature and SD print status (if SD printing) every 5 " +"seconds" +msgstr "" + +#: printrun/pronsole.py:1601 +msgid "" +"monitor 2 - Reports temperature and SD print status (if SD printing) every 2 " +"seconds" +msgstr "" + +#: printrun/pronsole.py:1612 +msgid "You must specify the tool index as an integer." +msgstr "" + +#: printrun/pronsole.py:1616 +msgid "Using tool %d." +msgstr "" + +#: printrun/pronsole.py:1620 +msgid "You cannot set negative tool numbers." +msgstr "" + +#: printrun/pronsole.py:1623 +msgid "" +"Switches to the specified tool (e.g. doing tool 1 will emit a T1 G-Code)." +msgstr "" + +#: printrun/pronsole.py:1627 +msgid "No move specified." +msgstr "" + +#: printrun/pronsole.py:1630 printrun/pronsole.py:1692 +#: printrun/pronsole.py:1735 printrun/pronsole.py:1762 +msgid "" +"Printer is currently printing. Please pause the print before you issue " +"manual commands." +msgstr "" + +#: printrun/pronsole.py:1633 printrun/pronsole.py:1759 +#, fuzzy +msgid "Printer is not online. Unable to move." +msgstr "Printer is niet verbonden." + +#: printrun/pronsole.py:1649 +msgid "Unknown axis." +msgstr "" + +#: printrun/pronsole.py:1654 +msgid "Invalid distance" +msgstr "" + +#: printrun/pronsole.py:1665 +msgid "Move an axis. Specify the name of the axis and the amount. " +msgstr "" + +#: printrun/pronsole.py:1666 +msgid "" +"move X 10 will move the X axis forward by 10mm at %s mm/min (default XY " +"speed)" +msgstr "" + +#: printrun/pronsole.py:1667 +msgid "move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min" +msgstr "" + +#: printrun/pronsole.py:1668 +msgid "" +"move Z -1 will move the Z axis down by 1mm at %s mm/min (default Z speed)" +msgstr "" + +#: printrun/pronsole.py:1669 +msgid "Common amounts are in the tabcomplete list." +msgstr "" + +#: printrun/pronsole.py:1699 printrun/pronsole.py:1742 +#, fuzzy +msgid "Invalid length given." +msgstr "Foute gegevens ingevoerd" + +#: printrun/pronsole.py:1704 printrun/pronsole.py:1747 +#, fuzzy +msgid "Invalid speed given." +msgstr "Foute gegevens ingevoerd" + +#: printrun/pronsole.py:1712 +msgid "Extruding %fmm of filament." +msgstr "" + +#: printrun/pronsole.py:1714 +msgid "Reversing %fmm of filament." +msgstr "" + +#: printrun/pronsole.py:1716 +msgid "Length is 0, not doing anything." +msgstr "" + +#: printrun/pronsole.py:1722 +msgid "" +"Extrudes a length of filament, 5mm by default, or the number of mm given as " +"a parameter" +msgstr "" + +#: printrun/pronsole.py:1723 +msgid "extrude - extrudes 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1724 +msgid "extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1725 +msgid "extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1726 +msgid "extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1732 +#, fuzzy +msgid "Printer is not online. Unable to reverse." +msgstr "Printer is niet verbonden." + +#: printrun/pronsole.py:1751 +msgid "" +"Reverses the extruder, 5mm by default, or the number of mm given as a " +"parameter" +msgstr "" + +#: printrun/pronsole.py:1752 +msgid "reverse - reverses 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1753 +msgid "reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1754 +msgid "reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1755 +msgid "reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1777 +#, fuzzy +msgid "Homes the printer" +msgstr "Verbind met printer" + +#: printrun/pronsole.py:1778 +msgid "home - homes all axes and zeroes the extruder(Using G28 and G92)" +msgstr "" + +#: printrun/pronsole.py:1779 +msgid "home xy - homes x and y axes (Using G28)" +msgstr "" + +#: printrun/pronsole.py:1780 +msgid "home z - homes z axis only (Using G28)" +msgstr "" + +#: printrun/pronsole.py:1781 +msgid "home e - set extruder position to zero (Using G92)" +msgstr "" + +#: printrun/pronsole.py:1782 +msgid "home xyze - homes all axes and zeroes the extruder (Using G28 and G92)" +msgstr "" + +#: printrun/pronsole.py:1790 +#, fuzzy +msgid "; Motors off" +msgstr "Motoren uit" + +#: printrun/pronsole.py:1792 +#, fuzzy +msgid "; Extruder off" +msgstr "Extruden" + +#: printrun/pronsole.py:1794 +msgid "; Heatbed off" +msgstr "" + +#: printrun/pronsole.py:1796 +msgid "; Fan off" +msgstr "" + +#: printrun/pronsole.py:1798 +msgid "; Power supply off" +msgstr "" + +#: printrun/pronsole.py:1801 +#, fuzzy +msgid "Printer is not online. Unable to turn it off." +msgstr "Printer is niet verbonden." + +#: printrun/pronsole.py:1804 +msgid "Turns off everything on the printer" +msgstr "" + +#: printrun/pronsole.py:1815 +msgid "G-Code calling host command \"%s\"" +msgstr "" + +#: printrun/pronsole.py:1824 +msgid "" +"Runs a custom script. Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronsole.py:1832 +msgid "" +"Runs a custom script which output gcode which will in turn be executed. " +"Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronterface.py:149 +msgid "Motors off" +msgstr "Motoren uit" + +#: printrun/pronterface.py:149 +msgid "Switch all motors off" +msgstr "" + +#: printrun/pronterface.py:150 +msgid "Advance extruder by set length" +msgstr "" + +#: printrun/pronterface.py:150 +msgid "Extrude" +msgstr "Extruden" + +#: printrun/pronterface.py:151 +msgid "Reverse" +msgstr "Terug" + +#: printrun/pronterface.py:151 +msgid "Reverse extruder by set length" +msgstr "" + +#: printrun/pronterface.py:165 +#, fuzzy +msgid "Pronterface" +msgstr "Printer Interface" + +#: printrun/pronterface.py:192 +msgid "" +"# I moved all your custom buttons into .pronsolerc.\n" +"# Please don't add them here any more.\n" +"# Backup of your old buttons is in custombtn.old\n" +msgstr "" +"# Gedefinieerde knoppen zijn verplaatst naar .pronsolerc.\n" +"# Hier geen nieuwe knoppen definiëren.\n" +"# Een backup van de oude knoppen staat in custombtn.old\n" + +#: printrun/pronterface.py:197 +msgid "" +"Note!!! You have specified custom buttons in both custombtn.txt and ." +"pronsolerc" +msgstr "" +"Let op!!! Er zijn gedefinieerde knoppen in zowel custombtn.txt en .pronsolerc" + +#: printrun/pronterface.py:198 +msgid "" +"Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt" +msgstr "" +"Negeer custombtn.txt. Verwijder alle gedefinieerde knoppen om gebruik te " +"maken van custombtn.txt" + +#: printrun/pronterface.py:331 +msgid "Monitoring printer." +msgstr "Printercommunicatie wordt gevolgd." + +#: printrun/pronterface.py:354 +msgid "Setting hotend temperature to %f degrees Celsius." +msgstr "Stel elementtemperatuur in op %f graden Celsius." + +#: printrun/pronterface.py:361 printrun/pronterface.py:381 +msgid "You must enter a temperature. (%s)" +msgstr "Er moet een temperatuur worden ingesteld. (%s)" + +#: printrun/pronterface.py:374 +msgid "Setting bed temperature to %f degrees Celsius." +msgstr "Bed teperatuur ingesteld op %f graden Celsius." + +#: printrun/pronterface.py:392 +msgid "Setting print speed factor to %d%%." +msgstr "" + +#: printrun/pronterface.py:396 +#, fuzzy +msgid "You must enter a speed. (%s)" +msgstr "Er moet een temperatuur worden ingesteld. (%s)" + +#: printrun/pronterface.py:470 +msgid "Plate function activated" +msgstr "" + +#: printrun/pronterface.py:479 +msgid "G-Code plate function activated" +msgstr "" + +#: printrun/pronterface.py:486 +msgid "Plated %s" +msgstr "" + +#: printrun/pronterface.py:500 +msgid "SD Upload" +msgstr "uploaden naar SD" + +#: printrun/pronterface.py:504 +msgid "SD Print" +msgstr "Afdruk van SD" + +#: printrun/pronterface.py:565 +msgid "" +"Manual move outside of the build volume prevented (see the \"Clamp manual " +"moves\" option)." +msgstr "" + +#: printrun/pronterface.py:621 +msgid "" +"Attempted to write invalid text to console, which could be due to an invalid " +"baudrate" +msgstr "" + +#: printrun/pronterface.py:655 +msgid " Opens file" +msgstr " Opent bestand" + +#: printrun/pronterface.py:655 +msgid "&Open..." +msgstr "&Open..." + +#: printrun/pronterface.py:663 +msgid " Clear output console" +msgstr "" + +#: printrun/pronterface.py:663 +msgid "Clear console" +msgstr "" + +#: printrun/pronterface.py:664 +msgid " Closes the Window" +msgstr " Sluit het venster" + +#: printrun/pronterface.py:664 +msgid "E&xit" +msgstr "&Stoppen" + +#: printrun/pronterface.py:665 +msgid "&File" +msgstr "" + +#: printrun/pronterface.py:668 +msgid " Edit open file" +msgstr " Wijzig geopend bestand" + +#: printrun/pronterface.py:668 +msgid "&Edit..." +msgstr "&Wijzig..." + +#: printrun/pronterface.py:669 +msgid " Compose 3D models into a single plate" +msgstr "" + +#: printrun/pronterface.py:669 +#, fuzzy +msgid "Plater" +msgstr "Element:" + +#: printrun/pronterface.py:670 +msgid " Compose G-Codes into a single plate" +msgstr "" + +#: printrun/pronterface.py:670 +msgid "G-Code Plater" +msgstr "" + +#: printrun/pronterface.py:671 +msgid " Exclude parts of the bed from being printed" +msgstr "" + +#: printrun/pronterface.py:671 +msgid "Excluder" +msgstr "" + +#: printrun/pronterface.py:672 +msgid " Project slices" +msgstr "" + +#: printrun/pronterface.py:672 +msgid "Projector" +msgstr "" + +#: printrun/pronterface.py:673 +msgid "&Tools" +msgstr "" + +#: printrun/pronterface.py:676 +msgid "" +" Recover previous print after a disconnect (homes X, Y, restores Z and E " +"status)" +msgstr "" + +#: printrun/pronterface.py:676 +msgid "Recover" +msgstr "" + +#: printrun/pronterface.py:679 +msgid "&Advanced" +msgstr "" + +#: printrun/pronterface.py:686 +#, fuzzy +msgid "Print &settings" +msgstr "Wijzig instellingen" + +#: printrun/pronterface.py:687 +msgid "&Filament" +msgstr "" + +#: printrun/pronterface.py:688 +#, fuzzy +msgid "&Printer" +msgstr "&Printen" + +#: printrun/pronterface.py:694 +msgid "&Slic3r" +msgstr "" + +#: printrun/pronterface.py:696 +msgid "Failed to load Slic3r configuration:" +msgstr "" + +#: printrun/pronterface.py:702 +msgid "&Macros" +msgstr "&Macros" + +#: printrun/pronterface.py:703 +msgid "<&New...>" +msgstr "<&Nieuw...>" + +#: printrun/pronterface.py:704 +msgid " Options dialog" +msgstr "Optievenster" + +#: printrun/pronterface.py:704 +msgid "&Options" +msgstr "&Opties" + +#: printrun/pronterface.py:706 +#, fuzzy +msgid " Adjust slicing settings" +msgstr "Instellen SFACT" + +#: printrun/pronterface.py:706 +#, fuzzy +msgid "Slicing settings" +msgstr "SFACT Instellingen" + +#: printrun/pronterface.py:708 +msgid "Debug communications" +msgstr "" + +#: printrun/pronterface.py:709 +msgid "Print all G-code sent to and received from the printer." +msgstr "" + +#: printrun/pronterface.py:713 +msgid "&Settings" +msgstr "&Instellingen" + +#: printrun/pronterface.py:719 +#, fuzzy +msgid "&About Printrun" +msgstr "Printercommunicatie volgen" + +#: printrun/pronterface.py:719 +msgid "Show about dialog" +msgstr "" + +#: printrun/pronterface.py:720 +msgid "&Help" +msgstr "" + +#: printrun/pronterface.py:746 +msgid "" +"Printrun is a pure Python 3D printing (and other types of CNC) host software." +msgstr "" + +#: printrun/pronterface.py:750 +#, fuzzy +msgid "%.02fmm of filament have been extruded during prints" +msgstr "mm fillament wordt gebruikt in deze print\n" + +#: printrun/pronterface.py:781 +#, fuzzy +msgid "Monitor printer status" +msgstr "Printercommunicatie volgen" + +#: printrun/pronterface.py:781 +msgid "" +"Regularly monitor printer temperatures (required to have functional " +"temperature graph or gauges)" +msgstr "" + +#: printrun/pronterface.py:782 +msgid "Path to the simarrange binary to use in the STL plater" +msgstr "" + +#: printrun/pronterface.py:782 +msgid "Simarrange command" +msgstr "" + +#: printrun/pronterface.py:783 +msgid "Circular build platform" +msgstr "" + +#: printrun/pronterface.py:783 +msgid "Draw a circular (or oval) build platform instead of a rectangular one" +msgstr "" + +#: printrun/pronterface.py:784 +#, fuzzy +msgid "Extruders count" +msgstr "Extruden" + +#: printrun/pronterface.py:784 +msgid "Number of extruders" +msgstr "" + +#: printrun/pronterface.py:785 +msgid "Clamp manual moves" +msgstr "" + +#: printrun/pronterface.py:785 +msgid "Prevent manual moves from leaving the specified build dimensions" +msgstr "" + +#: printrun/pronterface.py:786 +#, fuzzy +msgid "Interface mode" +msgstr "Voer macronaam in" + +#: printrun/pronterface.py:786 +msgid "" +"Standard interface is a one-page, three columns layout with controls/" +"visualization/log\n" +"Compact mode is a one-page, two columns layout with controls + log/" +"visualization\n" +"Tabbed mode is a two-pages mode, where the first page shows controls and the " +"second one shows visualization and log." +msgstr "" + +#: printrun/pronterface.py:787 +msgid "Controls mode" +msgstr "" + +#: printrun/pronterface.py:787 +msgid "" +"Standard controls include all controls needed for printer setup and " +"calibration, while Mini controls are limited to the ones needed for daily " +"printing" +msgstr "" + +#: printrun/pronterface.py:788 +msgid "Add a menu to select Slic3r profiles directly from Pronterface" +msgstr "" + +#: printrun/pronterface.py:788 +msgid "Enable Slic3r integration" +msgstr "" + +#: printrun/pronterface.py:789 +msgid "Update Slic3r default presets" +msgstr "" + +#: printrun/pronterface.py:789 +msgid "" +"When selecting a profile in Slic3r integration menu, also save it as the " +"default Slic3r preset" +msgstr "" + +#: printrun/pronterface.py:790 +msgid "Main visualization" +msgstr "" + +#: printrun/pronterface.py:790 +msgid "Select visualization for main window." +msgstr "" + +#: printrun/pronterface.py:791 +msgid "Use 3D in GCode viewer window" +msgstr "" + +#: printrun/pronterface.py:791 +msgid "Use 3D mode instead of 2D layered mode in the visualization window" +msgstr "" + +#: printrun/pronterface.py:792 +msgid "Use a lighter 3D visualization" +msgstr "" + +#: printrun/pronterface.py:792 +msgid "" +"Use a lighter visualization with simple lines instead of extruded paths for " +"3D viewer" +msgstr "" + +#: printrun/pronterface.py:793 +msgid "Track current layer in main 3D view" +msgstr "" + +#: printrun/pronterface.py:793 +msgid "Track the currently printing layer in the main 3D visualization" +msgstr "" + +#: printrun/pronterface.py:794 +msgid "Display temperature graph" +msgstr "" + +#: printrun/pronterface.py:794 +msgid "Display time-lapse temperature graph" +msgstr "" + +#: printrun/pronterface.py:795 +msgid "Display graphical gauges for temperatures visualization" +msgstr "" + +#: printrun/pronterface.py:795 +msgid "Display temperature gauges" +msgstr "" + +#: printrun/pronterface.py:796 +msgid "Display a checkbox that, when check, locks most of Pronterface" +msgstr "" + +#: printrun/pronterface.py:796 +msgid "Display interface lock checkbox" +msgstr "" + +#: printrun/pronterface.py:797 +msgid "If lock checkbox is enabled, lock the interface when starting a print" +msgstr "" + +#: printrun/pronterface.py:797 +msgid "Lock interface upon print start" +msgstr "" + +#: printrun/pronterface.py:804 +msgid "Preview extrusion width" +msgstr "" + +#: printrun/pronterface.py:804 +msgid "Width of Extrusion in Preview" +msgstr "" + +#: printrun/pronterface.py:805 +msgid "Fine Grid Spacing" +msgstr "" + +#: printrun/pronterface.py:805 +msgid "Fine grid spacing" +msgstr "" + +#: printrun/pronterface.py:806 +msgid "Coarse Grid Spacing" +msgstr "" + +#: printrun/pronterface.py:806 +msgid "Coarse grid spacing" +msgstr "" + +#: printrun/pronterface.py:807 +msgid "Background color" +msgstr "" + +#: printrun/pronterface.py:807 +msgid "Pronterface background color" +msgstr "" + +#: printrun/pronterface.py:808 +msgid "3D view background color" +msgstr "" + +#: printrun/pronterface.py:808 +msgid "Color of the 3D view background" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "3D view travel moves color" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "Color of travel moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "3D view print moves color" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "Color of print moves with tool 0 in 3D view" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "3D view tool 1 moves color" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "Color of print moves with tool 1 in 3D view" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "3D view printed moves color" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "Color of printed moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "3D view current layer moves color" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "Color of moves in current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "3D view printed current layer moves color" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "Color of already printed moves from current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:815 +msgid "Changing most settings here will require restart to get effect" +msgstr "" + +#: printrun/pronterface.py:815 +msgid "Note:" +msgstr "" + +#: printrun/pronterface.py:822 +msgid "automatically try to connect to printer on startup" +msgstr "" + +#: printrun/pronterface.py:835 printrun/pronterface.py:1316 +msgid "Failed to load recent files list:" +msgstr "" + +#: printrun/pronterface.py:916 +#, fuzzy +msgid "SD upload: %04.2f%% |" +msgstr " SD printen:%04.2f %%" + +#: printrun/pronterface.py:917 printrun/pronterface.py:922 +msgid " Line# %d of %d lines |" +msgstr "" + +#: printrun/pronterface.py:919 +#, fuzzy +msgid "SD printing: %04.2f%% |" +msgstr " SD printen:%04.2f %%" + +#: printrun/pronterface.py:921 +#, fuzzy +msgid "Printing: %04.2f%% |" +msgstr " Printen:%04.2f %% |" + +#: printrun/pronterface.py:924 +msgid " Est: %s of %s remaining | " +msgstr "" + +#: printrun/pronterface.py:926 +#, fuzzy +msgid " Z: %.3f mm" +msgstr " Z: %0.2f mm" + +#: printrun/pronterface.py:931 +msgid "Disconnecting after 4 failed writes." +msgstr "" + +#: printrun/pronterface.py:972 +#, fuzzy +msgid "Locking interface." +msgstr "Printer Interface" + +#: printrun/pronterface.py:976 +#, fuzzy +msgid "Unlocking interface." +msgstr "Printercommunicatie wordt gevolgd." + +#: printrun/pronterface.py:985 +#, fuzzy +msgid "Connecting..." +msgstr "Verbind" + +#: printrun/pronterface.py:997 +msgid "Could not parse baud rate: " +msgstr "" + +#: printrun/pronterface.py:1013 printrun/pronterface.py:1023 +msgid "Error: You are trying to connect to a non-existing port." +msgstr "" + +#: printrun/pronterface.py:1015 +msgid "Error: You don't have permission to open %s." +msgstr "" + +#: printrun/pronterface.py:1016 +msgid "You might need to add yourself to the dialout group." +msgstr "" + +#: printrun/pronterface.py:1043 +#, fuzzy +msgid "Disconnected." +msgstr "Ontkoppel" + +#: printrun/pronterface.py:1071 +#, fuzzy +msgid "Reset." +msgstr "Reset" + +#: printrun/pronterface.py:1072 +msgid "Are you sure you want to reset the printer?" +msgstr "Weet je zeker dat je de printer wilt resetten?" + +#: printrun/pronterface.py:1072 +msgid "Reset?" +msgstr "resetten?" + +#: printrun/pronterface.py:1093 +msgid "Restart" +msgstr "Herstart" + +#: printrun/pronterface.py:1126 +msgid "Pick SD filename" +msgstr "Kies bestandsnaam op SD" + +#: printrun/pronterface.py:1144 +msgid "File upload complete" +msgstr "Bestandsupload voltooid" + +#: printrun/pronterface.py:1151 +msgid "Print paused at: %s" +msgstr "" + +#: printrun/pronterface.py:1163 +msgid "Resume" +msgstr "Hervat" + +#: printrun/pronterface.py:1166 +#, fuzzy +msgid "Resuming." +msgstr "Hervat" + +#: printrun/pronterface.py:1192 +msgid "Pick SD file" +msgstr "Kies bestand op SD" + +#: printrun/pronterface.py:1192 +msgid "Select the file to print" +msgstr "Kies het te printen bestand" + +#: printrun/pronterface.py:1228 printrun/pronterface.py:1259 +#, fuzzy +msgid "Slicing " +msgstr "Skeinforge draait" + +#: printrun/pronterface.py:1237 +msgid "Failed to execute slicing software: " +msgstr "" + +#: printrun/pronterface.py:1244 +#, fuzzy +msgid "Slicing..." +msgstr "Skeinforge draait..." + +#: printrun/pronterface.py:1296 +msgid "Open file to print" +msgstr "Open het te printen bestand" + +#: printrun/pronterface.py:1297 +#, fuzzy +msgid "" +"OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*." +"gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*" +msgstr "STL en GCODE bestanden (;*.gcode;*.g;*.stl;*.STL;)" + +#: printrun/pronterface.py:1325 +msgid "Could not update recent files list:" +msgstr "" + +#: printrun/pronterface.py:1359 +msgid "Loaded %s, %d lines" +msgstr "Geladen %s, %d regels" + +#: printrun/pronterface.py:1362 +#, fuzzy +msgid "Load File" +msgstr "open bestand" + +#: printrun/pronterface.py:1376 +#, fuzzy +msgid "%.2fmm of filament used in this print" +msgstr "mm fillament wordt gebruikt in deze print\n" + +#: printrun/pronterface.py:1377 +msgid "The print goes:" +msgstr "" + +#: printrun/pronterface.py:1378 +msgid "- from %.2f mm to %.2f mm in X and is %.2f mm wide" +msgstr "" + +#: printrun/pronterface.py:1379 +msgid "- from %.2f mm to %.2f mm in Y and is %.2f mm deep" +msgstr "" + +#: printrun/pronterface.py:1380 +msgid "- from %.2f mm to %.2f mm in Z and is %.2f mm high" +msgstr "" + +#: printrun/pronterface.py:1450 +msgid "Printer is now online." +msgstr "Printer is nu verbonden." + +#: printrun/pronterface.py:1455 +msgid "Disconnect" +msgstr "Ontkoppel" + +#: printrun/pronterface.py:1688 +#, fuzzy +msgid "click to add new custom button" +msgstr "Definieer eigen knop." + +#: printrun/pronterface.py:1694 +msgid "Execute command: " +msgstr "" + +#: printrun/pronterface.py:1714 +msgid "" +"Defines custom button. Usage: button \"title\" [/c \"colour\"] command" +msgstr "" +"Definieert eigen knop. Gebruik: knop \"titel\" [/c \"kleur\"] commando" + +#: printrun/pronterface.py:1736 +msgid "Custom button number should be between 0 and 63" +msgstr "Knopnummer moet tussen 0 en 63 zijn" + +#: printrun/pronterface.py:1827 +msgid "Edit custom button '%s'" +msgstr "Wijzig gedefineerde knop '%s'" + +#: printrun/pronterface.py:1829 +msgid "Move left <<" +msgstr "Verplaats links <<" + +#: printrun/pronterface.py:1832 +msgid "Move right >>" +msgstr "Verplaats rechts >>" + +#: printrun/pronterface.py:1836 +msgid "Remove custom button '%s'" +msgstr "Verwijder gedefinieerde knop '%s'" + +#: printrun/pronterface.py:1839 +msgid "Add custom button" +msgstr "Definieer eigen knop." + +#: printrun/pronterface.py:1972 +msgid "event object missing" +msgstr "vermist object" + +#: printrun/pronterface.py:1985 +msgid "Do you want to erase the macro?" +msgstr "Wilt u de macro verwijderen?" + +#: printrun/pronterface.py:1989 +msgid "Cancelled." +msgstr "Afgebroken" + +#: printrun/pronterface.py:2007 +msgid "Enter macro name" +msgstr "Voer macronaam in" + +#: printrun/pronterface.py:2010 +msgid "Macro name:" +msgstr "Macronaam:" + +#: printrun/pronterface.py:2013 +msgid "Ok" +msgstr "Ok" + +#: printrun/pronterface.py:2035 +msgid "Macro name may contain only ASCII alphanumeric symbols and underscores" +msgstr "" + +#: printrun/pronterface.py:2038 +msgid "Name '%s' is being used by built-in command" +msgstr "Naam '%s' wordt gebruikt door ingebouwde instructie" + +#: pronsole.py:31 +msgid "Caught an exception, exiting:" +msgstr "" + +#~ msgid "Check temp" +#~ msgstr "Controleer Temp." + +#~ msgid "Mini mode" +#~ msgstr "Mini-venster" + +#~ msgid "Z:" +#~ msgstr "Z:" + +#~ msgid "Full mode" +#~ msgstr "Volledig venster" + +#~ msgid "Printer is online. " +#~ msgstr "Printer is verbonden. " + +#~ msgid "Bed" +#~ msgstr "Bed" + +#~ msgid "Hotend" +#~ msgstr "Element" + +#~ msgid ", %d lines" +#~ msgstr ", %d regels" + +#, fuzzy +#~ msgid "Paused." +#~ msgstr "Pauze" + +#~ msgid "X+100" +#~ msgstr "X+100" + +#~ msgid "X+10" +#~ msgstr "X+10" + +#~ msgid "X+1" +#~ msgstr "X+1" + +#~ msgid "X+0.1" +#~ msgstr "X+0.1" + +#~ msgid "HomeX" +#~ msgstr "0-puntX" + +#~ msgid "X-0.1" +#~ msgstr "X-0.1" + +#~ msgid "X-1" +#~ msgstr "X-1" + +#~ msgid "X-10" +#~ msgstr "X-10" + +#~ msgid "X-100" +#~ msgstr "X-100" + +#~ msgid "Y+100" +#~ msgstr "Y+100" + +#~ msgid "Y+10" +#~ msgstr "Y+10" + +#~ msgid "Y+1" +#~ msgstr "Y+1" + +#~ msgid "Y+0.1" +#~ msgstr "Y+0.1" + +#~ msgid "HomeY" +#~ msgstr "0-puntY" + +#~ msgid "Y-0.1" +#~ msgstr "Y-0.1" + +#~ msgid "Y-1" +#~ msgstr "Y-1" + +#~ msgid "Y-10" +#~ msgstr "Y-10" + +#~ msgid "Y-100" +#~ msgstr "Y-100" + +#~ msgid "Z+10" +#~ msgstr "Z+10" + +#~ msgid "Z+1" +#~ msgstr "Z+1" + +#~ msgid "Z+0.1" +#~ msgstr "Z+0.1" + +#~ msgid "HomeZ" +#~ msgstr "0-puntZ" + +#~ msgid "Z-0.1" +#~ msgstr "Z-0.1" + +#~ msgid "Z-1" +#~ msgstr "Z-1" + +#~ msgid "Z-10" +#~ msgstr "Z-10" + +#~ msgid "Home" +#~ msgstr "0-punt" + +#~ msgid " degrees Celsius." +#~ msgstr " graden Celsius." + +#~ msgid "SFACT Quick Settings" +#~ msgstr "SFACT Snelinstelling" + +#~ msgid " Quickly adjust SFACT settings for active profile" +#~ msgstr " Eenvoudig SFACT's huidige profiel instellen" + +#~ msgid "Name '" +#~ msgstr "Naam '" + +#~ msgid "Skeinforge execution failed." +#~ msgstr "Skeinforge was niet succesvol." + +#~ msgid "" +#~ "Skeinforge not found. \n" +#~ "Please copy Skeinforge into a directory named \"skeinforge\" in the same " +#~ "directory as this file." +#~ msgstr "" +#~ "Skeinforge niet gevonden.\n" +#~ "Plaats Skeinforge in een map met de naam \"skeinforge\" in dezelfde map " +#~ "als dit bestand." diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/plater.pot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/plater.pot Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,93 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2012-08-04 21:53+CEST\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" +"Generated-By: pygettext.py 1.5\n" + + +#: plater.py:246 +msgid "Plate building tool" +msgstr "" + +#: plater.py:252 +msgid "Clear" +msgstr "" + +#: plater.py:253 +msgid "Load" +msgstr "" + +#: plater.py:255 plater.py:258 +msgid "Export" +msgstr "" + +#: plater.py:260 +msgid "Done" +msgstr "" + +#: plater.py:262 +msgid "Cancel" +msgstr "" + +#: plater.py:264 +msgid "Snap to Z = 0" +msgstr "" + +#: plater.py:265 +msgid "Put at 100, 100" +msgstr "" + +#: plater.py:266 +msgid "Delete" +msgstr "" + +#: plater.py:267 +msgid "Auto" +msgstr "" + +#: plater.py:291 +msgid "Autoplating" +msgstr "" + +#: plater.py:319 +msgid "Bed full, sorry sir :(" +msgstr "" + +#: plater.py:329 +msgid "Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "" + +#: plater.py:329 +msgid "Clear the grid?" +msgstr "" + +#: plater.py:371 +msgid "Pick file to save to" +msgstr "" + +#: plater.py:372 +msgid "STL files (;*.stl;*.STL;)" +msgstr "" + +#: plater.py:393 +msgid "wrote %s" +msgstr "" + +#: plater.py:396 +msgid "Pick file to load" +msgstr "" + +#: plater.py:397 +msgid "STL files (;*.stl;*.STL;)|*.stl|OpenSCAD files (;*.scad;)|*.scad" +msgstr "" + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/locale/pronterface.pot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/locale/pronterface.pot Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,1970 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2014-03-30 14:35+CEST\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" +"Generated-By: pygettext.py 1.5\n" + + +#: printrun/excluder.py:26 +msgid "Part excluder: draw rectangles where print instructions should be ignored" +msgstr "" + +#: printrun/excluder.py:27 printrun/excluder.py:29 +msgid "Reset selection" +msgstr "" + +#: printrun/gcodeplater.py:43 printrun/gcodeplater.py:44 +msgid "GCODE files (*.gcode;*.GCODE;*.g)" +msgstr "" + +#: printrun/gcodeplater.py:112 printrun/gcodeplater.py:152 +msgid "Warning: no rotation support for now, object won't be correctly rotated" +msgstr "" + +#: printrun/gcodeplater.py:141 printrun/gcodeplater.py:178 +msgid "Exported merged G-Codes to %s" +msgstr "" + +#: printrun/gcview.py:352 +msgid "Fit to plate" +msgstr "" + +#: printrun/gcview.py:353 +msgid "Fit to plate [F]" +msgstr "" + +#: printrun/gui/__init__.py:19 printrun/pronterface.py:45 +msgid "WX is not installed. This program requires WX to run." +msgstr "" + +#: printrun/gui/__init__.py:159 +msgid "Commands" +msgstr "" + +#: printrun/gui/__init__.py:160 +msgid "Status" +msgstr "" + +#: printrun/gui/controls.py:126 +msgid "Heat:" +msgstr "" + +#: printrun/gui/controls.py:129 +msgid "Switch Hotend Off" +msgstr "" + +#: printrun/gui/controls.py:129 printrun/gui/controls.py:149 +#: printrun/gui/toolbar.py:85 +msgid "Off" +msgstr "" + +#: printrun/gui/controls.py:137 +msgid "Select Temperature for Hotend" +msgstr "" + +#: printrun/gui/controls.py:141 +msgid "Switch Hotend On" +msgstr "" + +#: printrun/gui/controls.py:141 printrun/gui/controls.py:161 +#: printrun/gui/controls.py:198 +msgid "Set" +msgstr "" + +#: printrun/gui/controls.py:146 printrun/gui/controls.py:215 +msgid "Bed:" +msgstr "" + +#: printrun/gui/controls.py:149 +msgid "Switch Heated Bed Off" +msgstr "" + +#: printrun/gui/controls.py:157 +msgid "Select Temperature for Heated Bed" +msgstr "" + +#: printrun/gui/controls.py:161 +msgid "Switch Heated Bed On" +msgstr "" + +#: printrun/gui/controls.py:187 +msgid "Print speed:" +msgstr "" + +#: printrun/gui/controls.py:192 printrun/gui/controls.py:207 +msgid "%d%%" +msgstr "" + +#: printrun/gui/controls.py:198 +msgid "Set print speed factor" +msgstr "" + +#: printrun/gui/controls.py:213 +msgid "Heater:" +msgstr "" + +#: printrun/gui/controls.py:265 +msgid "Length:" +msgstr "" + +#: printrun/gui/controls.py:267 +msgid "mm" +msgstr "" + +#: printrun/gui/controls.py:267 +msgid "mm @" +msgstr "" + +#: printrun/gui/controls.py:269 +msgid "Amount to Extrude or Retract (mm)" +msgstr "" + +#: printrun/gui/controls.py:272 +msgid "Extrude / Retract speed (mm/min)" +msgstr "" + +#: printrun/gui/controls.py:278 +msgid "Speed:" +msgstr "" + +#: printrun/gui/controls.py:279 +msgid "" +"mm/\n" +"min" +msgstr "" + +#: printrun/gui/controls.py:290 +msgid "Tool:" +msgstr "" + +#: printrun/gui/controls.py:293 +msgid "Click to switch current extruder" +msgstr "" + +#: printrun/gui/controls.py:310 +msgid "Select current extruder" +msgstr "" + +#: printrun/gui/controls.py:371 +msgid "Set Maximum Speed for X & Y axes (mm/min)" +msgstr "" + +#: printrun/gui/controls.py:372 +msgid "XY:" +msgstr "" + +#: printrun/gui/controls.py:374 +msgid "mm/min Z:" +msgstr "" + +#: printrun/gui/controls.py:376 +msgid "Set Maximum Speed for Z axis (mm/min)" +msgstr "" + +#: printrun/gui/graph.py:28 +msgid "Temperature graph" +msgstr "" + +#: printrun/gui/log.py:32 +msgid "" +"Send commands to printer\n" +"(Type 'help' for simple\n" +"help function)" +msgstr "" + +#: printrun/gui/log.py:39 +msgid "Send" +msgstr "" + +#: printrun/gui/log.py:39 +msgid "Send Command to Printer" +msgstr "" + +#: printrun/gui/toolbar.py:23 +msgid "Lock" +msgstr "" + +#: printrun/gui/toolbar.py:25 +msgid "Lock graphical interface" +msgstr "" + +#: printrun/gui/toolbar.py:32 +msgid "" +"Communication Settings\n" +"Click to rescan ports" +msgstr "" + +#: printrun/gui/toolbar.py:32 +msgid "Port" +msgstr "" + +#: printrun/gui/toolbar.py:37 +msgid "Select Port Printer is connected to" +msgstr "" + +#: printrun/gui/toolbar.py:46 +msgid "Select Baud rate for printer communication" +msgstr "" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1052 +msgid "Connect" +msgstr "" + +#: printrun/gui/toolbar.py:55 printrun/pronterface.py:1053 +msgid "Connect to the printer" +msgstr "" + +#: printrun/gui/toolbar.py:61 +msgid "Reset" +msgstr "" + +#: printrun/gui/toolbar.py:61 +msgid "Reset the printer" +msgstr "" + +#: printrun/gui/toolbar.py:69 +msgid "Load a 3D model file" +msgstr "" + +#: printrun/gui/toolbar.py:69 +msgid "Load file" +msgstr "" + +#: printrun/gui/toolbar.py:70 +msgid "SD" +msgstr "" + +#: printrun/gui/toolbar.py:70 +msgid "SD Card Printing" +msgstr "" + +#: printrun/gui/toolbar.py:74 +msgid "Start Printing Loaded File" +msgstr "" + +#: printrun/gui/toolbar.py:74 printrun/pronterface.py:1003 +#: printrun/pronterface.py:1062 printrun/pronterface.py:1078 +#: printrun/pronterface.py:1363 printrun/pronterface.py:1445 +msgid "Print" +msgstr "" + +#: printrun/gui/toolbar.py:80 +msgid "Pause Current Print" +msgstr "" + +#: printrun/gui/toolbar.py:80 printrun/pronterface.py:1002 +#: printrun/pronterface.py:1061 printrun/pronterface.py:1081 +#: printrun/pronterface.py:1091 printrun/pronterface.py:1172 +#: printrun/pronterface.py:1364 +msgid "Pause" +msgstr "" + +#: printrun/gui/toolbar.py:85 +msgid "Turn printer off" +msgstr "" + +#: printrun/gui/viz.py:65 +msgid "" +"Click to examine / edit\n" +" layers of loaded file" +msgstr "" + +#: printrun/gui/widgets.py:36 +msgid "Find" +msgstr "" + +#: printrun/gui/widgets.py:38 +msgid "Save" +msgstr "" + +#: printrun/gui/widgets.py:43 printrun/objectplater.py:76 +#: printrun/pronterface.py:1257 printrun/pronterface.py:2017 +msgid "Cancel" +msgstr "" + +#: printrun/gui/widgets.py:70 +msgid "Not Found!" +msgstr "" + +#: printrun/gui/widgets.py:124 +msgid "Printer settings" +msgstr "" + +#: printrun/gui/widgets.py:125 +msgid "User interface" +msgstr "" + +#: printrun/gui/widgets.py:126 +msgid "Colors" +msgstr "" + +#: printrun/gui/widgets.py:127 +msgid "External commands" +msgstr "" + +#: printrun/gui/widgets.py:132 +msgid "Edit settings" +msgstr "" + +#: printrun/gui/widgets.py:135 +msgid "Settings" +msgstr "" + +#: printrun/gui/widgets.py:187 +msgid "Custom button" +msgstr "" + +#: printrun/gui/widgets.py:193 +msgid "Button title" +msgstr "" + +#: printrun/gui/widgets.py:196 +msgid "Command" +msgstr "" + +#: printrun/gui/widgets.py:205 +msgid "Color" +msgstr "" + +#: printrun/gviz.py:29 +msgid "Layer number and Z position show here when you scroll" +msgstr "" + +#: printrun/gviz.py:39 +msgid "Zoom In [+]" +msgstr "" + +#: printrun/gviz.py:40 +msgid "Zoom Out [-]" +msgstr "" + +#: printrun/gviz.py:42 +msgid "Move Up a Layer [U]" +msgstr "" + +#: printrun/gviz.py:43 +msgid "Move Down a Layer [D]" +msgstr "" + +#: printrun/gviz.py:44 +msgid "Reset view" +msgstr "" + +#: printrun/gviz.py:68 +msgid "Gcode view, shift to move view, mousewheel to set layer" +msgstr "" + +#: printrun/gviz.py:105 printrun/gviz.py:241 +msgid "Layer %d - Going Up - Z = %.03f mm" +msgstr "" + +#: printrun/gviz.py:250 +msgid "Layer %d - Going Down - Z = %.03f mm" +msgstr "" + +#: printrun/objectplater.py:35 +msgid "Plate building tool" +msgstr "" + +#: printrun/objectplater.py:45 +msgid "Clear" +msgstr "" + +#: printrun/objectplater.py:49 +msgid "Load" +msgstr "" + +#: printrun/objectplater.py:53 +msgid "Snap to Z = 0" +msgstr "" + +#: printrun/objectplater.py:57 +msgid "Put at center" +msgstr "" + +#: printrun/objectplater.py:61 +msgid "Delete" +msgstr "" + +#: printrun/objectplater.py:65 +msgid "Auto arrange" +msgstr "" + +#: printrun/objectplater.py:69 +msgid "Export" +msgstr "" + +#: printrun/objectplater.py:73 +msgid "Done" +msgstr "" + +#: printrun/objectplater.py:151 +msgid "Autoplating" +msgstr "" + +#: printrun/objectplater.py:199 +msgid "Bed full, sorry sir :(" +msgstr "" + +#: printrun/objectplater.py:209 +msgid "Are you sure you want to clear the grid? All unsaved changes will be lost." +msgstr "" + +#: printrun/objectplater.py:210 +msgid "Clear the grid?" +msgstr "" + +#: printrun/objectplater.py:256 +msgid "Pick file to load" +msgstr "" + +#: printrun/objectplater.py:267 +msgid "Pick file to save to" +msgstr "" + +#: printrun/plater.py:212 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL|OpenSCAD files (*.scad)|*.scad" +msgstr "" + +#: printrun/plater.py:213 +msgid "STL files (*.stl;*.STL)|*.stl;*.STL" +msgstr "" + +#: printrun/plater.py:244 +msgid "Loading STL file failed" +msgstr "" + +#: printrun/plater.py:244 printrun/plater.py:251 +msgid "Error" +msgstr "" + +#: printrun/plater.py:251 +msgid "Loading OpenSCAD file failed" +msgstr "" + +#: printrun/plater.py:284 +msgid "Couldn't load non-existing file %s" +msgstr "" + +#: printrun/plater.py:360 +msgid "Wrote plate to %s" +msgstr "" + +#: printrun/plater.py:367 +msgid "Failed to use simarrange for plating, falling back to the standard method" +msgstr "" + +#: printrun/plater.py:372 +msgid "Autoplating using simarrange" +msgstr "" + +#: printrun/plater.py:387 +msgid "Plate full, please remove some objects" +msgstr "" + +#: printrun/plater.py:404 +msgid "simarrange failed" +msgstr "" + +#: printrun/printcore.py:177 +msgid "Could not connect to %s:%s:" +msgstr "" + +#: printrun/printcore.py:178 +msgid "Socket error %s:" +msgstr "" + +#: printrun/printcore.py:191 printrun/printcore.py:196 +msgid "Could not connect to %s at baudrate %s:" +msgstr "" + +#: printrun/printcore.py:192 +msgid "Serial error: %s" +msgstr "" + +#: printrun/printcore.py:197 +msgid "IO error: %s" +msgstr "" + +#: printrun/printcore.py:231 +msgid "Can't read from printer (disconnected?) (SelectError {0}): {1}" +msgstr "" + +#: printrun/printcore.py:234 +msgid "SelectError ({0}): {1}" +msgstr "" + +#: printrun/printcore.py:237 +msgid "Can't read from printer (disconnected?) (SerialException): {0}" +msgstr "" + +#: printrun/printcore.py:240 +msgid "Can't read from printer (disconnected?) (Socket error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:245 +msgid "Can't read from printer (disconnected?) (OS Error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:259 +msgid "Aborting connection attempt after 4 failed writes." +msgstr "" + +#: printrun/printcore.py:462 printrun/printcore.py:470 +#: printrun/pronsole.py:1192 printrun/pronsole.py:1250 +#: printrun/pronterface.py:209 printrun/pronterface.py:962 +#: printrun/pronterface.py:1111 printrun/pronterface.py:1178 +msgid "Not connected to printer." +msgstr "" + +#: printrun/printcore.py:479 +msgid "Print start callback failed with:" +msgstr "" + +#: printrun/printcore.py:490 +msgid "Print end callback failed with:" +msgstr "" + +#: printrun/printcore.py:493 +msgid "Print thread died due to the following error:" +msgstr "" + +#: printrun/printcore.py:584 +msgid "Could not analyze command %s:" +msgstr "" + +#: printrun/printcore.py:601 +msgid "Can't write to printer (disconnected ?):" +msgstr "" + +#: printrun/printcore.py:604 +msgid "Can't write to printer (disconnected?) (Socket error {0}): {1}" +msgstr "" + +#: printrun/printcore.py:607 +msgid "Can't write to printer (disconnected?) (SerialException): {0}" +msgstr "" + +#: printrun/printcore.py:610 +msgid "Socket connection broken, disconnected. ({0}): {1}" +msgstr "" + +#: printrun/pronsole.py:72 +msgid "Default: " +msgstr "" + +#: printrun/pronsole.py:73 +msgid "(Control-doubleclick to reset to default value)" +msgstr "" + +#: printrun/pronsole.py:108 +msgid "Are you sure you want to reset the setting to the default value: {0!r} ?" +msgstr "" + +#: printrun/pronsole.py:108 +msgid "Confirm set default" +msgstr "" + +#: printrun/pronsole.py:258 +msgid "Width" +msgstr "" + +#: printrun/pronsole.py:261 +msgid "Depth" +msgstr "" + +#: printrun/pronsole.py:264 +msgid "Height" +msgstr "" + +#: printrun/pronsole.py:267 +msgid "X offset" +msgstr "" + +#: printrun/pronsole.py:270 +msgid "Y offset" +msgstr "" + +#: printrun/pronsole.py:273 +msgid "Z offset" +msgstr "" + +#: printrun/pronsole.py:276 +msgid "X home pos." +msgstr "" + +#: printrun/pronsole.py:279 +msgid "Y home pos." +msgstr "" + +#: printrun/pronsole.py:282 +msgid "Z home pos." +msgstr "" + +#: printrun/pronsole.py:301 +msgid "Port used to communicate with printer" +msgstr "" + +#: printrun/pronsole.py:301 +msgid "Serial port" +msgstr "" + +#: printrun/pronsole.py:302 +msgid "Baud rate" +msgstr "" + +#: printrun/pronsole.py:302 +msgid "Communications Speed" +msgstr "" + +#: printrun/pronsole.py:303 +msgid "TCP streaming mode" +msgstr "" + +#: printrun/pronsole.py:303 +msgid "When using a TCP connection to the printer, the streaming mode will not wait for acks from the printer to send new commands. This will break things such as ETA prediction, but can result in smoother prints." +msgstr "" + +#: printrun/pronsole.py:304 +msgid "Bed temperature for ABS" +msgstr "" + +#: printrun/pronsole.py:304 +msgid "Heated Build Platform temp for ABS (deg C)" +msgstr "" + +#: printrun/pronsole.py:305 +msgid "Bed temperature for PLA" +msgstr "" + +#: printrun/pronsole.py:305 +msgid "Heated Build Platform temp for PLA (deg C)" +msgstr "" + +#: printrun/pronsole.py:306 +msgid "Extruder temp for ABS (deg C)" +msgstr "" + +#: printrun/pronsole.py:306 +msgid "Extruder temperature for ABS" +msgstr "" + +#: printrun/pronsole.py:307 +msgid "Extruder temp for PLA (deg C)" +msgstr "" + +#: printrun/pronsole.py:307 +msgid "Extruder temperature for PLA" +msgstr "" + +#: printrun/pronsole.py:308 +msgid "Feedrate for Control Panel Moves in X and Y (mm/min)" +msgstr "" + +#: printrun/pronsole.py:308 +msgid "X && Y manual feedrate" +msgstr "" + +#: printrun/pronsole.py:309 +msgid "Feedrate for Control Panel Moves in Z (mm/min)" +msgstr "" + +#: printrun/pronsole.py:309 +msgid "Z manual feedrate" +msgstr "" + +#: printrun/pronsole.py:310 +msgid "E manual feedrate" +msgstr "" + +#: printrun/pronsole.py:310 +msgid "Feedrate for Control Panel Moves in Extrusions (mm/min)" +msgstr "" + +#: printrun/pronsole.py:311 +msgid "Slice command" +msgstr "" + +#: printrun/pronsole.py:312 +msgid "Slice settings command" +msgstr "" + +#: printrun/pronsole.py:312 +msgid "Slicer options command" +msgstr "" + +#: printrun/pronsole.py:313 +msgid "Executable to run when the print is finished" +msgstr "" + +#: printrun/pronsole.py:313 +msgid "Final command" +msgstr "" + +#: printrun/pronsole.py:314 +msgid "Error command" +msgstr "" + +#: printrun/pronsole.py:314 +msgid "Executable to run when an error occurs" +msgstr "" + +#: printrun/pronsole.py:381 +msgid "Failed to run callback after setting \"%s\":" +msgstr "" + +#: printrun/pronsole.py:469 +msgid "Build dimensions" +msgstr "" + +#: printrun/pronsole.py:469 +msgid "" +"Dimensions of Build Platform\n" +" & optional offset of origin\n" +" & optional switch position\n" +"\n" +"Examples:\n" +" XXXxYYY\n" +" XXX,YYY,ZZZ\n" +" XXXxYYYxZZZ+OffX+OffY+OffZ\n" +"XXXxYYYxZZZ+OffX+OffY+OffZ+HomeX+HomeY+HomeZ" +msgstr "" + +#: printrun/pronsole.py:495 +msgid "Welcome to the printer console! Type \"help\" for a list of available commands." +msgstr "" + +#: printrun/pronsole.py:657 printrun/pronsole.py:665 printrun/pronsole.py:673 +#: printrun/pronsole.py:1519 printrun/pronsole.py:1546 +#: printrun/pronsole.py:1618 printrun/pronterface.py:357 +#: printrun/pronterface.py:377 printrun/pronterface.py:394 +msgid "Printer is not online." +msgstr "" + +#: printrun/pronsole.py:692 +msgid "Exiting program. Goodbye!" +msgstr "" + +#: printrun/pronsole.py:697 +msgid "Disconnects from the printer and exits the program." +msgstr "" + +#: printrun/pronsole.py:974 +msgid "load this file on startup instead of .pronsolerc ; you may chain config files, if so settings auto-save will use the last specified file" +msgstr "" + +#: printrun/pronsole.py:975 +msgid "executes command after configuration/.pronsolerc is loaded ; macros/settings from these commands are not autosaved" +msgstr "" + +#: printrun/pronsole.py:976 +msgid "file to load" +msgstr "" + +#: printrun/pronsole.py:1104 +msgid "Loaded %s, %d lines." +msgstr "" + +#: printrun/pronsole.py:1105 printrun/pronterface.py:1381 +msgid "Estimated duration: %d layers, %s" +msgstr "" + +#: printrun/pronsole.py:1134 +msgid "No file name given." +msgstr "" + +#: printrun/pronsole.py:1140 +msgid "Skeining file: %s" +msgstr "" + +#: printrun/pronsole.py:1142 printrun/pronterface.py:1305 +msgid "File not found!" +msgstr "" + +#: printrun/pronsole.py:1147 +msgid "Entering slicer settings: %s" +msgstr "" + +#: printrun/pronsole.py:1151 +msgid "Slicing: " +msgstr "" + +#: printrun/pronsole.py:1158 +msgid "Loading sliced file." +msgstr "" + +#: printrun/pronsole.py:1161 +msgid "Slicing failed: %s" +msgstr "" + +#: printrun/pronsole.py:1174 +msgid "Creates a gcode file from an stl model using the slicer (with tab-completion)" +msgstr "" + +#: printrun/pronsole.py:1175 +msgid "slice filename.stl - create gcode file" +msgstr "" + +#: printrun/pronsole.py:1176 +msgid "slice filename.stl view - create gcode file and view using skeiniso (if using skeinforge)" +msgstr "" + +#: printrun/pronsole.py:1177 +msgid "slice set - adjust slicer settings" +msgstr "" + +#: printrun/pronsole.py:1189 +msgid "Please enter target name in 8.3 format." +msgstr "" + +#: printrun/pronsole.py:1195 +msgid "Uploading as %s" +msgstr "" + +#: printrun/pronsole.py:1196 +msgid "Uploading %s" +msgstr "" + +#: printrun/pronsole.py:1198 +msgid "Press Ctrl-C to interrupt upload." +msgstr "" + +#: printrun/pronsole.py:1201 +msgid "Progress: " +msgstr "" + +#: printrun/pronsole.py:1212 +msgid "Upload completed. %s should now be on the card." +msgstr "" + +#: printrun/pronsole.py:1216 +msgid "...interrupted!" +msgstr "" + +#: printrun/pronsole.py:1218 +msgid "Something wrong happened while uploading:" +msgstr "" + +#: printrun/pronsole.py:1224 +msgid "A partial file named %s may have been written to the sd card." +msgstr "" + +#: printrun/pronsole.py:1241 +msgid "Send a loaded gcode file to the printer. Load a file with the load command first." +msgstr "" + +#: printrun/pronsole.py:1243 +msgid "Send a loaded gcode file to the printer. You have %s loaded right now." +msgstr "" + +#: printrun/pronsole.py:1247 printrun/pronterface.py:730 +#: printrun/pronterface.py:1108 +msgid "No file loaded. Please use load first." +msgstr "" + +#: printrun/pronsole.py:1252 +msgid "Printing %s" +msgstr "" + +#: printrun/pronsole.py:1253 +msgid "You can monitor the print with the monitor command." +msgstr "" + +#: printrun/pronsole.py:1261 +msgid "Not printing, cannot pause." +msgstr "" + +#: printrun/pronsole.py:1267 +msgid "Pauses a running print" +msgstr "" + +#: printrun/pronsole.py:1274 +msgid "Not paused, unable to resume. Start a print first." +msgstr "" + +#: printrun/pronsole.py:1284 +msgid "Resumes a paused print." +msgstr "" + +#: printrun/pronsole.py:1293 +msgid "Files on SD card:" +msgstr "" + +#: printrun/pronsole.py:1307 printrun/pronsole.py:1349 +#: printrun/pronsole.py:1562 +msgid "Printer is not online. Please connect to it first." +msgstr "" + +#: printrun/pronsole.py:1312 +msgid "Lists files on the SD card" +msgstr "" + +#: printrun/pronsole.py:1316 printrun/pronterface.py:1645 +msgid "Opening file failed." +msgstr "" + +#: printrun/pronsole.py:1322 printrun/pronterface.py:1651 +msgid "Starting print" +msgstr "" + +#: printrun/pronsole.py:1345 +msgid "Resets the printer." +msgstr "" + +#: printrun/pronsole.py:1355 +msgid "File is not present on card. Please upload it first." +msgstr "" + +#: printrun/pronsole.py:1359 +msgid "Printing file: %s from SD card." +msgstr "" + +#: printrun/pronsole.py:1360 +msgid "Requesting SD print..." +msgstr "" + +#: printrun/pronsole.py:1364 +msgid "Print a file from the SD card. Tab completes with available file names." +msgstr "" + +#: printrun/pronsole.py:1365 +msgid "sdprint filename.g" +msgstr "" + +#: printrun/pronsole.py:1382 +msgid "Print resumed at: %s" +msgstr "" + +#: printrun/pronsole.py:1384 +msgid "Print started at: %s" +msgstr "" + +#: printrun/pronsole.py:1392 +msgid "Failed to inhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1399 +msgid "Failed to uninhibit sleep:" +msgstr "" + +#: printrun/pronsole.py:1403 +msgid "Print ended at: %(end_time)s and took %(duration)s" +msgstr "" + +#: printrun/pronsole.py:1457 +msgid "Printer is not currently printing. No ETA available." +msgstr "" + +#: printrun/pronsole.py:1460 +msgid "Est: %s of %s remaining" +msgstr "" + +#: printrun/pronsole.py:1465 +msgid "Displays estimated remaining print time." +msgstr "" + +#: printrun/pronsole.py:1498 +msgid "Read the extruder and bed temperature." +msgstr "" + +#: printrun/pronsole.py:1507 printrun/pronsole.py:1540 +msgid "You must enter a temperature." +msgstr "" + +#: printrun/pronsole.py:1512 +msgid "%s is a high temperature to set your extruder to. Are you sure you want to do that?" +msgstr "" + +#: printrun/pronsole.py:1517 +msgid "Setting hotend temperature to %s degrees Celsius." +msgstr "" + +#: printrun/pronsole.py:1521 printrun/pronterface.py:359 +msgid "You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0." +msgstr "" + +#: printrun/pronsole.py:1524 +msgid "Sets the hotend temperature to the value entered." +msgstr "" + +#: printrun/pronsole.py:1525 printrun/pronsole.py:1552 +msgid "Enter either a temperature in celsius or one of the following keywords" +msgstr "" + +#: printrun/pronsole.py:1544 +msgid "Setting bed temperature to %s degrees Celsius." +msgstr "" + +#: printrun/pronsole.py:1548 printrun/pronterface.py:379 +msgid "You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0." +msgstr "" + +#: printrun/pronsole.py:1551 +msgid "Sets the bed temperature to the value entered." +msgstr "" + +#: printrun/pronsole.py:1565 +msgid "Printer is not printing. Please print something before monitoring." +msgstr "" + +#: printrun/pronsole.py:1567 +msgid "Monitoring printer, use ^C to interrupt." +msgstr "" + +#: printrun/pronsole.py:1572 printrun/pronterface.py:329 +msgid "Invalid period given." +msgstr "" + +#: printrun/pronsole.py:1573 +msgid "Updating values every %f seconds." +msgstr "" + +#: printrun/pronsole.py:1584 printrun/pronsole.py:1587 +msgid "Print progress: " +msgstr "" + +#: printrun/pronsole.py:1595 printrun/pronterface.py:333 +msgid "Done monitoring." +msgstr "" + +#: printrun/pronsole.py:1599 +msgid "Monitor a machine's temperatures and an SD print's status." +msgstr "" + +#: printrun/pronsole.py:1600 +msgid "monitor - Reports temperature and SD print status (if SD printing) every 5 seconds" +msgstr "" + +#: printrun/pronsole.py:1601 +msgid "monitor 2 - Reports temperature and SD print status (if SD printing) every 2 seconds" +msgstr "" + +#: printrun/pronsole.py:1612 +msgid "You must specify the tool index as an integer." +msgstr "" + +#: printrun/pronsole.py:1616 +msgid "Using tool %d." +msgstr "" + +#: printrun/pronsole.py:1620 +msgid "You cannot set negative tool numbers." +msgstr "" + +#: printrun/pronsole.py:1623 +msgid "Switches to the specified tool (e.g. doing tool 1 will emit a T1 G-Code)." +msgstr "" + +#: printrun/pronsole.py:1627 +msgid "No move specified." +msgstr "" + +#: printrun/pronsole.py:1630 printrun/pronsole.py:1692 +#: printrun/pronsole.py:1735 printrun/pronsole.py:1762 +msgid "Printer is currently printing. Please pause the print before you issue manual commands." +msgstr "" + +#: printrun/pronsole.py:1633 printrun/pronsole.py:1759 +msgid "Printer is not online. Unable to move." +msgstr "" + +#: printrun/pronsole.py:1649 +msgid "Unknown axis." +msgstr "" + +#: printrun/pronsole.py:1654 +msgid "Invalid distance" +msgstr "" + +#: printrun/pronsole.py:1665 +msgid "Move an axis. Specify the name of the axis and the amount. " +msgstr "" + +#: printrun/pronsole.py:1666 +msgid "move X 10 will move the X axis forward by 10mm at %s mm/min (default XY speed)" +msgstr "" + +#: printrun/pronsole.py:1667 +msgid "move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min" +msgstr "" + +#: printrun/pronsole.py:1668 +msgid "move Z -1 will move the Z axis down by 1mm at %s mm/min (default Z speed)" +msgstr "" + +#: printrun/pronsole.py:1669 +msgid "Common amounts are in the tabcomplete list." +msgstr "" + +#: printrun/pronsole.py:1699 printrun/pronsole.py:1742 +msgid "Invalid length given." +msgstr "" + +#: printrun/pronsole.py:1704 printrun/pronsole.py:1747 +msgid "Invalid speed given." +msgstr "" + +#: printrun/pronsole.py:1712 +msgid "Extruding %fmm of filament." +msgstr "" + +#: printrun/pronsole.py:1714 +msgid "Reversing %fmm of filament." +msgstr "" + +#: printrun/pronsole.py:1716 +msgid "Length is 0, not doing anything." +msgstr "" + +#: printrun/pronsole.py:1722 +msgid "Extrudes a length of filament, 5mm by default, or the number of mm given as a parameter" +msgstr "" + +#: printrun/pronsole.py:1723 +msgid "extrude - extrudes 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1724 +msgid "extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1725 +msgid "extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1726 +msgid "extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1732 +msgid "Printer is not online. Unable to reverse." +msgstr "" + +#: printrun/pronsole.py:1751 +msgid "Reverses the extruder, 5mm by default, or the number of mm given as a parameter" +msgstr "" + +#: printrun/pronsole.py:1752 +msgid "reverse - reverses 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1753 +msgid "reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1754 +msgid "reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1755 +msgid "reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)" +msgstr "" + +#: printrun/pronsole.py:1777 +msgid "Homes the printer" +msgstr "" + +#: printrun/pronsole.py:1778 +msgid "home - homes all axes and zeroes the extruder(Using G28 and G92)" +msgstr "" + +#: printrun/pronsole.py:1779 +msgid "home xy - homes x and y axes (Using G28)" +msgstr "" + +#: printrun/pronsole.py:1780 +msgid "home z - homes z axis only (Using G28)" +msgstr "" + +#: printrun/pronsole.py:1781 +msgid "home e - set extruder position to zero (Using G92)" +msgstr "" + +#: printrun/pronsole.py:1782 +msgid "home xyze - homes all axes and zeroes the extruder (Using G28 and G92)" +msgstr "" + +#: printrun/pronsole.py:1790 +msgid "; Motors off" +msgstr "" + +#: printrun/pronsole.py:1792 +msgid "; Extruder off" +msgstr "" + +#: printrun/pronsole.py:1794 +msgid "; Heatbed off" +msgstr "" + +#: printrun/pronsole.py:1796 +msgid "; Fan off" +msgstr "" + +#: printrun/pronsole.py:1798 +msgid "; Power supply off" +msgstr "" + +#: printrun/pronsole.py:1801 +msgid "Printer is not online. Unable to turn it off." +msgstr "" + +#: printrun/pronsole.py:1804 +msgid "Turns off everything on the printer" +msgstr "" + +#: printrun/pronsole.py:1815 +msgid "G-Code calling host command \"%s\"" +msgstr "" + +#: printrun/pronsole.py:1824 +msgid "Runs a custom script. Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronsole.py:1832 +msgid "Runs a custom script which output gcode which will in turn be executed. Current gcode filename can be given using %s token." +msgstr "" + +#: printrun/pronterface.py:149 +msgid "Motors off" +msgstr "" + +#: printrun/pronterface.py:149 +msgid "Switch all motors off" +msgstr "" + +#: printrun/pronterface.py:150 +msgid "Advance extruder by set length" +msgstr "" + +#: printrun/pronterface.py:150 +msgid "Extrude" +msgstr "" + +#: printrun/pronterface.py:151 +msgid "Reverse" +msgstr "" + +#: printrun/pronterface.py:151 +msgid "Reverse extruder by set length" +msgstr "" + +#: printrun/pronterface.py:165 +msgid "Pronterface" +msgstr "" + +#: printrun/pronterface.py:192 +msgid "" +"# I moved all your custom buttons into .pronsolerc.\n" +"# Please don't add them here any more.\n" +"# Backup of your old buttons is in custombtn.old\n" +msgstr "" + +#: printrun/pronterface.py:197 +msgid "Note!!! You have specified custom buttons in both custombtn.txt and .pronsolerc" +msgstr "" + +#: printrun/pronterface.py:198 +msgid "Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt" +msgstr "" + +#: printrun/pronterface.py:331 +msgid "Monitoring printer." +msgstr "" + +#: printrun/pronterface.py:354 +msgid "Setting hotend temperature to %f degrees Celsius." +msgstr "" + +#: printrun/pronterface.py:361 printrun/pronterface.py:381 +msgid "You must enter a temperature. (%s)" +msgstr "" + +#: printrun/pronterface.py:374 +msgid "Setting bed temperature to %f degrees Celsius." +msgstr "" + +#: printrun/pronterface.py:392 +msgid "Setting print speed factor to %d%%." +msgstr "" + +#: printrun/pronterface.py:396 +msgid "You must enter a speed. (%s)" +msgstr "" + +#: printrun/pronterface.py:470 +msgid "Plate function activated" +msgstr "" + +#: printrun/pronterface.py:479 +msgid "G-Code plate function activated" +msgstr "" + +#: printrun/pronterface.py:486 +msgid "Plated %s" +msgstr "" + +#: printrun/pronterface.py:500 +msgid "SD Upload" +msgstr "" + +#: printrun/pronterface.py:504 +msgid "SD Print" +msgstr "" + +#: printrun/pronterface.py:565 +msgid "Manual move outside of the build volume prevented (see the \"Clamp manual moves\" option)." +msgstr "" + +#: printrun/pronterface.py:621 +msgid "Attempted to write invalid text to console, which could be due to an invalid baudrate" +msgstr "" + +#: printrun/pronterface.py:655 +msgid " Opens file" +msgstr "" + +#: printrun/pronterface.py:655 +msgid "&Open..." +msgstr "" + +#: printrun/pronterface.py:663 +msgid " Clear output console" +msgstr "" + +#: printrun/pronterface.py:663 +msgid "Clear console" +msgstr "" + +#: printrun/pronterface.py:664 +msgid " Closes the Window" +msgstr "" + +#: printrun/pronterface.py:664 +msgid "E&xit" +msgstr "" + +#: printrun/pronterface.py:665 +msgid "&File" +msgstr "" + +#: printrun/pronterface.py:668 +msgid " Edit open file" +msgstr "" + +#: printrun/pronterface.py:668 +msgid "&Edit..." +msgstr "" + +#: printrun/pronterface.py:669 +msgid " Compose 3D models into a single plate" +msgstr "" + +#: printrun/pronterface.py:669 +msgid "Plater" +msgstr "" + +#: printrun/pronterface.py:670 +msgid " Compose G-Codes into a single plate" +msgstr "" + +#: printrun/pronterface.py:670 +msgid "G-Code Plater" +msgstr "" + +#: printrun/pronterface.py:671 +msgid " Exclude parts of the bed from being printed" +msgstr "" + +#: printrun/pronterface.py:671 +msgid "Excluder" +msgstr "" + +#: printrun/pronterface.py:672 +msgid " Project slices" +msgstr "" + +#: printrun/pronterface.py:672 +msgid "Projector" +msgstr "" + +#: printrun/pronterface.py:673 +msgid "&Tools" +msgstr "" + +#: printrun/pronterface.py:676 +msgid " Recover previous print after a disconnect (homes X, Y, restores Z and E status)" +msgstr "" + +#: printrun/pronterface.py:676 +msgid "Recover" +msgstr "" + +#: printrun/pronterface.py:679 +msgid "&Advanced" +msgstr "" + +#: printrun/pronterface.py:686 +msgid "Print &settings" +msgstr "" + +#: printrun/pronterface.py:687 +msgid "&Filament" +msgstr "" + +#: printrun/pronterface.py:688 +msgid "&Printer" +msgstr "" + +#: printrun/pronterface.py:694 +msgid "&Slic3r" +msgstr "" + +#: printrun/pronterface.py:696 +msgid "Failed to load Slic3r configuration:" +msgstr "" + +#: printrun/pronterface.py:702 +msgid "&Macros" +msgstr "" + +#: printrun/pronterface.py:703 +msgid "<&New...>" +msgstr "" + +#: printrun/pronterface.py:704 +msgid " Options dialog" +msgstr "" + +#: printrun/pronterface.py:704 +msgid "&Options" +msgstr "" + +#: printrun/pronterface.py:706 +msgid " Adjust slicing settings" +msgstr "" + +#: printrun/pronterface.py:706 +msgid "Slicing settings" +msgstr "" + +#: printrun/pronterface.py:708 +msgid "Debug communications" +msgstr "" + +#: printrun/pronterface.py:709 +msgid "Print all G-code sent to and received from the printer." +msgstr "" + +#: printrun/pronterface.py:713 +msgid "&Settings" +msgstr "" + +#: printrun/pronterface.py:719 +msgid "&About Printrun" +msgstr "" + +#: printrun/pronterface.py:719 +msgid "Show about dialog" +msgstr "" + +#: printrun/pronterface.py:720 +msgid "&Help" +msgstr "" + +#: printrun/pronterface.py:746 +msgid "Printrun is a pure Python 3D printing (and other types of CNC) host software." +msgstr "" + +#: printrun/pronterface.py:750 +msgid "%.02fmm of filament have been extruded during prints" +msgstr "" + +#: printrun/pronterface.py:781 +msgid "Monitor printer status" +msgstr "" + +#: printrun/pronterface.py:781 +msgid "Regularly monitor printer temperatures (required to have functional temperature graph or gauges)" +msgstr "" + +#: printrun/pronterface.py:782 +msgid "Path to the simarrange binary to use in the STL plater" +msgstr "" + +#: printrun/pronterface.py:782 +msgid "Simarrange command" +msgstr "" + +#: printrun/pronterface.py:783 +msgid "Circular build platform" +msgstr "" + +#: printrun/pronterface.py:783 +msgid "Draw a circular (or oval) build platform instead of a rectangular one" +msgstr "" + +#: printrun/pronterface.py:784 +msgid "Extruders count" +msgstr "" + +#: printrun/pronterface.py:784 +msgid "Number of extruders" +msgstr "" + +#: printrun/pronterface.py:785 +msgid "Clamp manual moves" +msgstr "" + +#: printrun/pronterface.py:785 +msgid "Prevent manual moves from leaving the specified build dimensions" +msgstr "" + +#: printrun/pronterface.py:786 +msgid "Interface mode" +msgstr "" + +#: printrun/pronterface.py:786 +msgid "" +"Standard interface is a one-page, three columns layout with controls/visualization/log\n" +"Compact mode is a one-page, two columns layout with controls + log/visualization\n" +"Tabbed mode is a two-pages mode, where the first page shows controls and the second one shows visualization and log." +msgstr "" + +#: printrun/pronterface.py:787 +msgid "Controls mode" +msgstr "" + +#: printrun/pronterface.py:787 +msgid "Standard controls include all controls needed for printer setup and calibration, while Mini controls are limited to the ones needed for daily printing" +msgstr "" + +#: printrun/pronterface.py:788 +msgid "Add a menu to select Slic3r profiles directly from Pronterface" +msgstr "" + +#: printrun/pronterface.py:788 +msgid "Enable Slic3r integration" +msgstr "" + +#: printrun/pronterface.py:789 +msgid "Update Slic3r default presets" +msgstr "" + +#: printrun/pronterface.py:789 +msgid "When selecting a profile in Slic3r integration menu, also save it as the default Slic3r preset" +msgstr "" + +#: printrun/pronterface.py:790 +msgid "Main visualization" +msgstr "" + +#: printrun/pronterface.py:790 +msgid "Select visualization for main window." +msgstr "" + +#: printrun/pronterface.py:791 +msgid "Use 3D in GCode viewer window" +msgstr "" + +#: printrun/pronterface.py:791 +msgid "Use 3D mode instead of 2D layered mode in the visualization window" +msgstr "" + +#: printrun/pronterface.py:792 +msgid "Use a lighter 3D visualization" +msgstr "" + +#: printrun/pronterface.py:792 +msgid "Use a lighter visualization with simple lines instead of extruded paths for 3D viewer" +msgstr "" + +#: printrun/pronterface.py:793 +msgid "Track current layer in main 3D view" +msgstr "" + +#: printrun/pronterface.py:793 +msgid "Track the currently printing layer in the main 3D visualization" +msgstr "" + +#: printrun/pronterface.py:794 +msgid "Display temperature graph" +msgstr "" + +#: printrun/pronterface.py:794 +msgid "Display time-lapse temperature graph" +msgstr "" + +#: printrun/pronterface.py:795 +msgid "Display graphical gauges for temperatures visualization" +msgstr "" + +#: printrun/pronterface.py:795 +msgid "Display temperature gauges" +msgstr "" + +#: printrun/pronterface.py:796 +msgid "Display a checkbox that, when check, locks most of Pronterface" +msgstr "" + +#: printrun/pronterface.py:796 +msgid "Display interface lock checkbox" +msgstr "" + +#: printrun/pronterface.py:797 +msgid "If lock checkbox is enabled, lock the interface when starting a print" +msgstr "" + +#: printrun/pronterface.py:797 +msgid "Lock interface upon print start" +msgstr "" + +#: printrun/pronterface.py:804 +msgid "Preview extrusion width" +msgstr "" + +#: printrun/pronterface.py:804 +msgid "Width of Extrusion in Preview" +msgstr "" + +#: printrun/pronterface.py:805 +msgid "Fine Grid Spacing" +msgstr "" + +#: printrun/pronterface.py:805 +msgid "Fine grid spacing" +msgstr "" + +#: printrun/pronterface.py:806 +msgid "Coarse Grid Spacing" +msgstr "" + +#: printrun/pronterface.py:806 +msgid "Coarse grid spacing" +msgstr "" + +#: printrun/pronterface.py:807 +msgid "Background color" +msgstr "" + +#: printrun/pronterface.py:807 +msgid "Pronterface background color" +msgstr "" + +#: printrun/pronterface.py:808 +msgid "3D view background color" +msgstr "" + +#: printrun/pronterface.py:808 +msgid "Color of the 3D view background" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "3D view travel moves color" +msgstr "" + +#: printrun/pronterface.py:809 +msgid "Color of travel moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "3D view print moves color" +msgstr "" + +#: printrun/pronterface.py:810 +msgid "Color of print moves with tool 0 in 3D view" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "3D view tool 1 moves color" +msgstr "" + +#: printrun/pronterface.py:811 +msgid "Color of print moves with tool 1 in 3D view" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "3D view printed moves color" +msgstr "" + +#: printrun/pronterface.py:812 +msgid "Color of printed moves in 3D view" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "3D view current layer moves color" +msgstr "" + +#: printrun/pronterface.py:813 +msgid "Color of moves in current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "3D view printed current layer moves color" +msgstr "" + +#: printrun/pronterface.py:814 +msgid "Color of already printed moves from current layer in 3D view" +msgstr "" + +#: printrun/pronterface.py:815 +msgid "Changing most settings here will require restart to get effect" +msgstr "" + +#: printrun/pronterface.py:815 +msgid "Note:" +msgstr "" + +#: printrun/pronterface.py:822 +msgid "automatically try to connect to printer on startup" +msgstr "" + +#: printrun/pronterface.py:835 printrun/pronterface.py:1316 +msgid "Failed to load recent files list:" +msgstr "" + +#: printrun/pronterface.py:916 +msgid "SD upload: %04.2f%% |" +msgstr "" + +#: printrun/pronterface.py:917 printrun/pronterface.py:922 +msgid " Line# %d of %d lines |" +msgstr "" + +#: printrun/pronterface.py:919 +msgid "SD printing: %04.2f%% |" +msgstr "" + +#: printrun/pronterface.py:921 +msgid "Printing: %04.2f%% |" +msgstr "" + +#: printrun/pronterface.py:924 +msgid " Est: %s of %s remaining | " +msgstr "" + +#: printrun/pronterface.py:926 +msgid " Z: %.3f mm" +msgstr "" + +#: printrun/pronterface.py:931 +msgid "Disconnecting after 4 failed writes." +msgstr "" + +#: printrun/pronterface.py:972 +msgid "Locking interface." +msgstr "" + +#: printrun/pronterface.py:976 +msgid "Unlocking interface." +msgstr "" + +#: printrun/pronterface.py:985 +msgid "Connecting..." +msgstr "" + +#: printrun/pronterface.py:997 +msgid "Could not parse baud rate: " +msgstr "" + +#: printrun/pronterface.py:1013 printrun/pronterface.py:1023 +msgid "Error: You are trying to connect to a non-existing port." +msgstr "" + +#: printrun/pronterface.py:1015 +msgid "Error: You don't have permission to open %s." +msgstr "" + +#: printrun/pronterface.py:1016 +msgid "You might need to add yourself to the dialout group." +msgstr "" + +#: printrun/pronterface.py:1043 +msgid "Disconnected." +msgstr "" + +#: printrun/pronterface.py:1071 +msgid "Reset." +msgstr "" + +#: printrun/pronterface.py:1072 +msgid "Are you sure you want to reset the printer?" +msgstr "" + +#: printrun/pronterface.py:1072 +msgid "Reset?" +msgstr "" + +#: printrun/pronterface.py:1093 +msgid "Restart" +msgstr "" + +#: printrun/pronterface.py:1126 +msgid "Pick SD filename" +msgstr "" + +#: printrun/pronterface.py:1144 +msgid "File upload complete" +msgstr "" + +#: printrun/pronterface.py:1151 +msgid "Print paused at: %s" +msgstr "" + +#: printrun/pronterface.py:1163 +msgid "Resume" +msgstr "" + +#: printrun/pronterface.py:1166 +msgid "Resuming." +msgstr "" + +#: printrun/pronterface.py:1192 +msgid "Pick SD file" +msgstr "" + +#: printrun/pronterface.py:1192 +msgid "Select the file to print" +msgstr "" + +#: printrun/pronterface.py:1228 printrun/pronterface.py:1259 +msgid "Slicing " +msgstr "" + +#: printrun/pronterface.py:1237 +msgid "Failed to execute slicing software: " +msgstr "" + +#: printrun/pronterface.py:1244 +msgid "Slicing..." +msgstr "" + +#: printrun/pronterface.py:1296 +msgid "Open file to print" +msgstr "" + +#: printrun/pronterface.py:1297 +msgid "OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*" +msgstr "" + +#: printrun/pronterface.py:1325 +msgid "Could not update recent files list:" +msgstr "" + +#: printrun/pronterface.py:1359 +msgid "Loaded %s, %d lines" +msgstr "" + +#: printrun/pronterface.py:1362 +msgid "Load File" +msgstr "" + +#: printrun/pronterface.py:1376 +msgid "%.2fmm of filament used in this print" +msgstr "" + +#: printrun/pronterface.py:1377 +msgid "The print goes:" +msgstr "" + +#: printrun/pronterface.py:1378 +msgid "- from %.2f mm to %.2f mm in X and is %.2f mm wide" +msgstr "" + +#: printrun/pronterface.py:1379 +msgid "- from %.2f mm to %.2f mm in Y and is %.2f mm deep" +msgstr "" + +#: printrun/pronterface.py:1380 +msgid "- from %.2f mm to %.2f mm in Z and is %.2f mm high" +msgstr "" + +#: printrun/pronterface.py:1450 +msgid "Printer is now online." +msgstr "" + +#: printrun/pronterface.py:1455 +msgid "Disconnect" +msgstr "" + +#: printrun/pronterface.py:1688 +msgid "click to add new custom button" +msgstr "" + +#: printrun/pronterface.py:1694 +msgid "Execute command: " +msgstr "" + +#: printrun/pronterface.py:1714 +msgid "Defines custom button. Usage: button \"title\" [/c \"colour\"] command" +msgstr "" + +#: printrun/pronterface.py:1736 +msgid "Custom button number should be between 0 and 63" +msgstr "" + +#: printrun/pronterface.py:1827 +msgid "Edit custom button '%s'" +msgstr "" + +#: printrun/pronterface.py:1829 +msgid "Move left <<" +msgstr "" + +#: printrun/pronterface.py:1832 +msgid "Move right >>" +msgstr "" + +#: printrun/pronterface.py:1836 +msgid "Remove custom button '%s'" +msgstr "" + +#: printrun/pronterface.py:1839 +msgid "Add custom button" +msgstr "" + +#: printrun/pronterface.py:1972 +msgid "event object missing" +msgstr "" + +#: printrun/pronterface.py:1985 +msgid "Do you want to erase the macro?" +msgstr "" + +#: printrun/pronterface.py:1989 +msgid "Cancelled." +msgstr "" + +#: printrun/pronterface.py:2007 +msgid "Enter macro name" +msgstr "" + +#: printrun/pronterface.py:2010 +msgid "Macro name:" +msgstr "" + +#: printrun/pronterface.py:2013 +msgid "Ok" +msgstr "" + +#: printrun/pronterface.py:2035 +msgid "Macro name may contain only ASCII alphanumeric symbols and underscores" +msgstr "" + +#: printrun/pronterface.py:2038 +msgid "Name '%s' is being used by built-in command" +msgstr "" + +#: pronsole.py:31 +msgid "Caught an exception, exiting:" +msgstr "" + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/plater.appdata.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/plater.appdata.xml Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,14 @@ + + + plater.desktop + CC0 + 3D printer plating tool + +

Plater is a simple graphical tool for preparing plates for desktop 3D printers, such as RepRap.

+

It lets you load 3D models and manipulate them on XY plane. It is also possible to let Plater automatically arrange the files to fit. You can than export the prepared plate for later use or load it directly to Pronterface (if installed).

+
+ + https://raw.github.com/kliment/Printrun/master/screenshots/plater.png + + https://github.com/kliment/Printrun +
diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/plater.desktop --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/plater.desktop Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=Plater +GenericName=Plate building tool +Comment=Prepares plates for 3D printing +Icon=/usr/share/pixmaps/plater.png +Exec=/usr/bin/plater.py +StartupNotify=true +Terminal=false +Categories=GNOME;GTK;Utility;Graphics;3DGraphics; diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/plater.ico Binary file printrun-src/plater.ico has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/plater.png Binary file printrun-src/plater.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/plater.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/plater.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import sys +import wx +import getopt + +from printrun.stlplater import StlPlater + +if __name__ == '__main__': + + from printrun.printcore import __version__ as printcore_version + + usage = "Usage:\n"+\ + " plater [OPTION]\n"+\ + " plater FILES\n\n"+\ + "Options:\n"+\ + " -V, --version\t\t\tPrint program's version number and exit\n"+\ + " -h, --help\t\t\tPrint this help message and exit\n" + + try: + opts, args = getopt.getopt(sys.argv[1:], "hV", ["help", "version"]) + except getopt.GetoptError, err: + print str(err) + print usage + sys.exit(2) + for o, a in opts: + if o in ('-V','--version'): + print "printrun "+printcore_version + sys.exit(0) + elif o in ('-h', '--help'): + print usage + sys.exit(0) + + app = wx.App(False) + main = StlPlater(filenames = sys.argv[1:]) + main.Show() + app.MainLoop() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printcore.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printcore.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,106 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import time +import getopt +import sys +import getopt + +from printrun.printcore import printcore +from printrun.utils import setup_logging +from printrun import gcoder + +if __name__ == '__main__': + setup_logging(sys.stderr) + baud = 115200 + loud = False + statusreport = False + + from printrun.printcore import __version__ as printcore_version + + usage = "Usage:\n"+\ + " printcore [OPTIONS] PORT FILE\n\n"+\ + "Options:\n"+\ + " -b, --baud=BAUD_RATE"+\ + "\t\tSet baud rate value. Default value is 115200\n"+\ + " -s, --statusreport\t\tPrint progress as percentage\n"+\ + " -v, --verbose\t\t\tPrint additional progress information\n"+\ + " -V, --version\t\t\tPrint program's version number and exit\n"+\ + " -h, --help\t\t\tPrint this help message and exit\n" + + try: + opts, args = getopt.getopt(sys.argv[1:], "b:svVh", + ["baud=", "statusreport", "verbose", "version", "help"]) + except getopt.GetoptError, err: + print str(err) + print usage + sys.exit(2) + for o, a in opts: + if o in ('-h', '--help'): + print usage + sys.exit(0) + elif o in ('-V','--version'): + print "printrun "+printcore_version + sys.exit(0) + elif o in ('-b','--baud'): + try: + baud = int(a) + except ValueError: + print "ValueError:" + print "\tInvalid BAUD_RATE value '%s'" % a + print "\tBAUD_RATE must be an integer\n" + # FIXME: This should output a more apropiate error message when + # not a good baud rate is passed as an argument + # i.e: when baud <= 1000 or > 225000 + print usage + sys.exit(2) + elif o in ('-v', '--verbose'): + loud = True + elif o in ('-s', '--statusreport'): + statusreport = True + + if len(args) <= 1: + print "Error: Port or gcode file were not specified.\n" + print usage + sys.exit(2) + elif len(args) > 1: + port = args[-2] + filename = args[-1] + print "Printing: %s on %s with baudrate %d" % (filename, port, baud) + + p = printcore(port, baud) + p.loud = loud + time.sleep(2) + gcode = [i.strip() for i in open(filename)] + gcode = gcoder.LightGCode(gcode) + p.startprint(gcode) + + try: + if statusreport: + p.loud = False + sys.stdout.write("Progress: 00.0%\r") + sys.stdout.flush() + while p.printing: + time.sleep(1) + if statusreport: + progress = 100 * float(p.queueindex) / len(p.mainqueue) + sys.stdout.write("Progress: %02.1f%%\r" % progress) + sys.stdout.flush() + p.disconnect() + sys.exit(0) + except: + p.disconnect() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/__init__.py diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/excluder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/excluder.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,128 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx +from printrun import gviz + +from .utils import imagefile, install_locale +install_locale('pronterface') + +class ExcluderWindow(gviz.GvizWindow): + + def __init__(self, excluder, *args, **kwargs): + super(ExcluderWindow, self).__init__(*args, **kwargs) + self.SetTitle(_("Part excluder: draw rectangles where print instructions should be ignored")) + self.toolbar.AddLabelTool(128, " " + _("Reset selection"), + wx.Image(imagefile('reset.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), + shortHelp = _("Reset selection"), + longHelp = "") + self.Bind(wx.EVT_TOOL, self.reset_selection, id = 128) + self.parent = excluder + self.p.paint_overlay = self.paint_selection + self.p.layerup() + + def real_to_gcode(self, x, y): + return (x + self.p.build_dimensions[3], + self.p.build_dimensions[4] + self.p.build_dimensions[1] - y) + + def gcode_to_real(self, x, y): + return (x - self.p.build_dimensions[3], + self.p.build_dimensions[1] - (y - self.p.build_dimensions[4])) + + def mouse(self, event): + if event.ButtonUp(wx.MOUSE_BTN_LEFT) \ + or event.ButtonUp(wx.MOUSE_BTN_RIGHT): + self.initpos = None + elif event.Dragging() and event.RightIsDown(): + e = event.GetPositionTuple() + if not self.initpos or not hasattr(self, "basetrans"): + self.initpos = e + self.basetrans = self.p.translate + self.p.translate = [self.basetrans[0] + (e[0] - self.initpos[0]), + self.basetrans[1] + (e[1] - self.initpos[1])] + self.p.dirty = 1 + wx.CallAfter(self.p.Refresh) + elif event.Dragging() and event.LeftIsDown(): + x, y = event.GetPositionTuple() + if not self.initpos: + self.basetrans = self.p.translate + x = (x - self.basetrans[0]) / self.p.scale[0] + y = (y - self.basetrans[1]) / self.p.scale[1] + x, y = self.real_to_gcode(x, y) + if not self.initpos: + self.initpos = (x, y) + self.parent.rectangles.append((0, 0, 0, 0)) + else: + pos = (x, y) + x0 = min(self.initpos[0], pos[0]) + y0 = min(self.initpos[1], pos[1]) + x1 = max(self.initpos[0], pos[0]) + y1 = max(self.initpos[1], pos[1]) + self.parent.rectangles[-1] = (x0, y0, x1, y1) + wx.CallAfter(self.p.Refresh) + else: + event.Skip() + + def _line_scaler(self, orig): + x0, y0 = self.gcode_to_real(orig[0], orig[1]) + x0 = self.p.scale[0] * x0 + self.p.translate[0] + y0 = self.p.scale[1] * y0 + self.p.translate[1] + x1, y1 = self.gcode_to_real(orig[2], orig[3]) + x1 = self.p.scale[0] * x1 + self.p.translate[0] + y1 = self.p.scale[1] * y1 + self.p.translate[1] + width = max(x0, x1) - min(x0, x1) + 1 + height = max(y0, y1) - min(y0, y1) + 1 + return (min(x0, x1), min(y0, y1), width, height,) + + def paint_selection(self, dc): + dc = wx.GCDC(dc) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.DrawRectangleList([self._line_scaler(rect) + for rect in self.parent.rectangles], + None, wx.Brush((200, 200, 200, 150))) + + def reset_selection(self, event): + self.parent.rectangles = [] + wx.CallAfter(self.p.Refresh) + +class Excluder(object): + + def __init__(self): + self.rectangles = [] + self.window = None + + def pop_window(self, gcode, *args, **kwargs): + if not self.window: + self.window = ExcluderWindow(self, *args, **kwargs) + self.window.p.addfile(gcode, True) + self.window.Bind(wx.EVT_CLOSE, self.close_window) + self.window.Show() + else: + self.window.Show() + self.window.Raise() + + def close_window(self, event = None): + if self.window: + self.window.Destroy() + self.window = None + +if __name__ == '__main__': + import sys + import gcoder + gcode = gcoder.GCode(open(sys.argv[1])) + app = wx.App(False) + ex = Excluder() + ex.pop_window(gcode) + app.MainLoop() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gcodeplater.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gcodeplater.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,247 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +# Set up Internationalization using gettext +# searching for installed locales on /usr/share; uses relative folder if not found (windows) +from .utils import install_locale, get_home_pos +install_locale('pronterface') + +import wx +import sys +import os +import time +import types +import re +import math +import logging + +from printrun import gcoder +from printrun.objectplater import make_plater, PlaterPanel +from printrun.gl.libtatlin import actors +import printrun.gui.viz # NOQA +from printrun import gcview + +def extrusion_only(gline): + return gline.e is not None \ + and (gline.x, gline.y, gline.z) == (None, None, None) + +# Custom method for gcoder.GCode to analyze & output gcode in a single call +def gcoder_write(self, f, line, store = False): + f.write(line) + self.append(line, store = store) + +rewrite_exp = re.compile("(%s)" % "|".join(["X([-+]?[0-9]*\.?[0-9]*)", + "Y([-+]?[0-9]*\.?[0-9]*)"])) + +def rewrite_gline(centeroffset, gline, cosr, sinr): + if gline.is_move and (gline.x is not None or gline.y is not None): + if gline.relative: + xc = yc = 0 + cox = coy = 0 + if gline.x is not None: + xc = gline.x + if gline.y is not None: + yc = gline.y + else: + xc = gline.current_x + centeroffset[0] + yc = gline.current_y + centeroffset[1] + cox = centeroffset[0] + coy = centeroffset[1] + new_x = "X%.04f" % (xc * cosr - yc * sinr - cox) + new_y = "Y%.04f" % (xc * sinr + yc * cosr - coy) + new = {"X": new_x, "Y": new_y} + new_line = rewrite_exp.sub(lambda ax: new[ax.group()[0]], gline.raw) + new_line = new_line.split(";")[0] + if gline.x is None: new_line += " " + new_x + if gline.y is None: new_line += " " + new_y + return new_line + else: + return gline.raw + +class GcodePlaterPanel(PlaterPanel): + + load_wildcard = _("GCODE files (*.gcode;*.GCODE;*.g)") + "|*.gcode;*.gco;*.g" + save_wildcard = _("GCODE files (*.gcode;*.GCODE;*.g)") + "|*.gcode;*.gco;*.g" + + def prepare_ui(self, filenames = [], callback = None, + parent = None, build_dimensions = None, + circular_platform = False, antialias_samples = 0): + super(GcodePlaterPanel, self).prepare_ui(filenames, callback, parent, build_dimensions) + viewer = gcview.GcodeViewPanel(self, build_dimensions = self.build_dimensions, + antialias_samples = antialias_samples) + self.set_viewer(viewer) + self.platform = actors.Platform(self.build_dimensions, + circular = circular_platform) + self.platform_object = gcview.GCObject(self.platform) + + def get_objects(self): + return [self.platform_object] + self.models.values() + objects = property(get_objects) + + def load_file(self, filename): + gcode = gcoder.GCode(open(filename, "rU"), + get_home_pos(self.build_dimensions)) + model = actors.GcodeModel() + if gcode.filament_length > 0: + model.display_travels = False + generator = model.load_data(gcode) + generator_output = generator.next() + while generator_output is not None: + generator_output = generator.next() + obj = gcview.GCObject(model) + obj.offsets = [self.build_dimensions[3], self.build_dimensions[4], 0] + obj.gcode = gcode + obj.dims = [gcode.xmin, gcode.xmax, + gcode.ymin, gcode.ymax, + gcode.zmin, gcode.zmax] + obj.centeroffset = [-(obj.dims[1] + obj.dims[0]) / 2, + -(obj.dims[3] + obj.dims[2]) / 2, + 0] + self.add_model(filename, obj) + wx.CallAfter(self.Refresh) + + def done(self, event, cb): + if not os.path.exists("tempgcode"): + os.mkdir("tempgcode") + name = "tempgcode/" + str(int(time.time()) % 10000) + ".gcode" + self.export_to(name) + if cb is not None: + cb(name) + if self.destroy_on_done: + self.Destroy() + + # What's hard in there ? + # 1) [x] finding the order in which the objects are printed + # 2) [x] handling layers correctly + # 3) [x] handling E correctly + # 4) [x] handling position shifts: should we either reset absolute 0 using + # G92 or should we rewrite all positions ? => we use G92s + # 5) [ ] handling the start & end gcode properly ? + # 6) [x] handling of current tool + # 7) [x] handling of Z moves for sequential printing (don't lower Z before + # reaching the next object print area) + # 8) [x] handling of absolute/relative status + # Initial implementation should just print the objects sequentially, + # but the end goal is to have a clean per-layer merge + def export_to(self, name): + return self.export_combined(name) + return self.export_sequential(name) + + def export_combined(self, name): + models = self.models.values() + last_real_position = None + # Sort models by Z max to print smaller objects first + models.sort(key = lambda x: x.dims[-1]) + alllayers = [] + for (model_i, model) in enumerate(models): + def add_offset(layer): + return layer.z + model.offsets[2] if layer.z is not None else layer.z + alllayers += [(add_offset(layer), model_i, layer_i) + for (layer_i, layer) in enumerate(model.gcode.all_layers) if layer] + alllayers.sort() + laste = [0] * len(models) + lasttool = [0] * len(models) + lastrelative = [False] * len(models) + with open(name, "w") as f: + analyzer = gcoder.GCode(None, get_home_pos(self.build_dimensions)) + analyzer.write = types.MethodType(lambda self, line: gcoder_write(self, f, line), analyzer) + for (layer_z, model_i, layer_i) in alllayers: + model = models[model_i] + layer = model.gcode.all_layers[layer_i] + r = math.radians(model.rot) + o = model.offsets + co = model.centeroffset + offset_pos = last_real_position if last_real_position is not None else (0, 0, 0) + analyzer.write("; %f %f %f\n" % offset_pos) + trans = (- (o[0] + co[0]), + - (o[1] + co[1]), + - (o[2] + co[2])) + trans_wpos = (offset_pos[0] + trans[0], + offset_pos[1] + trans[1], + offset_pos[2] + trans[2]) + analyzer.write("; GCodePlater: Model %d Layer %d at Z = %s\n" % (model_i, layer_i, layer_z)) + if lastrelative[model_i]: + analyzer.write("G91\n") + else: + analyzer.write("G90\n") + if analyzer.current_tool != lasttool[model_i]: + analyzer.write("T%d\n" % lasttool[model_i]) + analyzer.write("G92 X%.5f Y%.5f Z%.5f\n" % trans_wpos) + analyzer.write("G92 E%.5f\n" % laste[model_i]) + for l in layer: + if l.command != "G28" and (l.command != "G92" or extrusion_only(l)): + if r == 0: + analyzer.write(l.raw + "\n") + else: + analyzer.write(rewrite_gline(co, l, math.cos(r), math.sin(r)) + "\n") + # Find the current real position & E + last_real_position = analyzer.current_pos + laste[model_i] = analyzer.current_e + lastrelative[model_i] = analyzer.relative + lasttool[model_i] = analyzer.current_tool + logging.info(_("Exported merged G-Codes to %s") % name) + + def export_sequential(self, name): + models = self.models.values() + last_real_position = None + # Sort models by Z max to print smaller objects first + models.sort(key = lambda x: x.dims[-1]) + with open(name, "w") as f: + for model_i, model in enumerate(models): + r = math.radians(model.rot) + o = model.offsets + co = model.centeroffset + offset_pos = last_real_position if last_real_position is not None else (0, 0, 0) + trans = (- (o[0] + co[0]), + - (o[1] + co[1]), + - (o[2] + co[2])) + trans_wpos = (offset_pos[0] + trans[0], + offset_pos[1] + trans[1], + offset_pos[2] + trans[2]) + f.write("; GCodePlater: Model %d\n" % model_i) + f.write("G90\n") + f.write("G92 X%.5f Y%.5f Z%.5f E0\n" % trans_wpos) + f.write("G1 X%.5f Y%.5f" % (-co[0], -co[1])) + for l in model.gcode: + if l.command != "G28" and (l.command != "G92" or extrusion_only(l)): + if r == 0: + f.write(l.raw + "\n") + else: + f.write(rewrite_gline(co, l, math.cos(r), math.sin(r)) + "\n") + # Find the current real position + for i in xrange(len(model.gcode) - 1, -1, -1): + gline = model.gcode.lines[i] + if gline.is_move: + last_real_position = (- trans[0] + gline.current_x, + - trans[1] + gline.current_y, + - trans[2] + gline.current_z) + break + logging.info(_("Exported merged G-Codes to %s") % name) + +GcodePlater = make_plater(GcodePlaterPanel) + +if __name__ == '__main__': + app = wx.App(False) + main = GcodePlater(filenames = sys.argv[1:]) + for fn in main.filenames: + main.load_file(fn) + main.filenames = None + main.autoplate() + main.export_to("gcodeplate___test.gcode") + raise SystemExit + main.Show() + app.MainLoop() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gcoder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gcoder.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,755 @@ +#!/usr/bin/env python +# This file is copied from GCoder. +# +# GCoder is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GCoder is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import sys +import re +import math +import datetime +import logging +from array import array + +gcode_parsed_args = ["x", "y", "e", "f", "z", "i", "j"] +gcode_parsed_nonargs = ["g", "t", "m", "n"] +to_parse = "".join(gcode_parsed_args + gcode_parsed_nonargs) +gcode_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n|([%s])([-+]?[0-9]*\.?[0-9]*)" % to_parse) +gcode_strip_comment_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n") +m114_exp = re.compile("\([^\(\)]*\)|[/\*].*\n|([XYZ]):?([-+]?[0-9]*\.?[0-9]*)") +specific_exp = "(?:\([^\(\)]*\))|(?:;.*)|(?:[/\*].*\n)|(%s[-+]?[0-9]*\.?[0-9]*)" +move_gcodes = ["G0", "G1", "G2", "G3"] + +class PyLine(object): + + __slots__ = ('x', 'y', 'z', 'e', 'f', 'i', 'j', + 'raw', 'command', 'is_move', + 'relative', 'relative_e', + 'current_x', 'current_y', 'current_z', 'extruding', + 'current_tool', + 'gcview_end_vertex') + + def __init__(self, l): + self.raw = l + + def __getattr__(self, name): + return None + +class PyLightLine(object): + + __slots__ = ('raw', 'command') + + def __init__(self, l): + self.raw = l + + def __getattr__(self, name): + return None + +try: + import gcoder_line + Line = gcoder_line.GLine + LightLine = gcoder_line.GLightLine +except Exception, e: + logging.warning("Memory-efficient GCoder implementation unavailable: %s" % e) + Line = PyLine + LightLine = PyLightLine + +def find_specific_code(line, code): + exp = specific_exp % code + bits = [bit for bit in re.findall(exp, line.raw) if bit] + if not bits: return None + else: return float(bits[0][1:]) + +def S(line): + return find_specific_code(line, "S") + +def P(line): + return find_specific_code(line, "P") + +def split(line): + split_raw = gcode_exp.findall(line.raw.lower()) + if split_raw and split_raw[0][0] == "n": + del split_raw[0] + if not split_raw: + line.command = line.raw + line.is_move = False + logging.warning("raw G-Code line \"%s\" could not be parsed" % line.raw) + return [line.raw] + command = split_raw[0] + line.command = command[0].upper() + command[1] + line.is_move = line.command in move_gcodes + return split_raw + +def parse_coordinates(line, split_raw, imperial = False, force = False): + # Not a G-line, we don't want to parse its arguments + if not force and line.command[0] != "G": + return + unit_factor = 25.4 if imperial else 1 + for bit in split_raw: + code = bit[0] + if code not in gcode_parsed_nonargs and bit[1]: + setattr(line, code, unit_factor * float(bit[1])) + +class Layer(list): + + __slots__ = ("duration", "z") + + def __init__(self, lines, z = None): + super(Layer, self).__init__(lines) + self.z = z + +class GCode(object): + + line_class = Line + + lines = None + layers = None + all_layers = None + layer_idxs = None + line_idxs = None + append_layer = None + append_layer_id = None + + imperial = False + relative = False + relative_e = False + current_tool = 0 + # Home position: current absolute position counted from machine origin + home_x = 0 + home_y = 0 + home_z = 0 + # Current position: current absolute position counted from machine origin + current_x = 0 + current_y = 0 + current_z = 0 + # For E this is the absolute position from machine start + current_e = 0 + current_e_multi=[0] + total_e = 0 + total_e_multi=[0] + max_e = 0 + max_e_multi=[0] + # Current feedrate + current_f = 0 + # Offset: current offset between the machine origin and the machine current + # absolute coordinate system (as shifted by G92s) + offset_x = 0 + offset_y = 0 + offset_z = 0 + offset_e = 0 + offset_e_multi = [0] + + # Expected behavior: + # - G28 X => X axis is homed, offset_x <- 0, current_x <- home_x + # - G92 Xk => X axis does not move, so current_x does not change + # and offset_x <- current_x - k, + # - absolute G1 Xk => X axis moves, current_x <- offset_x + k + # How to get... + # current abs X from machine origin: current_x + # current abs X in machine current coordinate system: current_x - offset_x + + filament_length = None + filament_length_multi=[0] + duration = None + xmin = None + xmax = None + ymin = None + ymax = None + zmin = None + zmax = None + width = None + depth = None + height = None + + est_layer_height = None + + # abs_x is the current absolute X in machine current coordinate system + # (after the various G92 transformations) and can be used to store the + # absolute position of the head at a given time + def _get_abs_x(self): + return self.current_x - self.offset_x + abs_x = property(_get_abs_x) + + def _get_abs_y(self): + return self.current_y - self.offset_y + abs_y = property(_get_abs_y) + + def _get_abs_z(self): + return self.current_z - self.offset_z + abs_z = property(_get_abs_z) + + def _get_abs_e(self): + return self.current_e - self.offset_e + abs_e = property(_get_abs_e) + + def _get_abs_e_multi(self,i): + return self.current_e_multi[i] - self.offset_e_multi[i] + abs_e = property(_get_abs_e) + + def _get_abs_pos(self): + return (self.abs_x, self.abs_y, self.abs_z) + abs_pos = property(_get_abs_pos) + + def _get_current_pos(self): + return (self.current_x, self.current_y, self.current_z) + current_pos = property(_get_current_pos) + + def _get_home_pos(self): + return (self.home_x, self.home_y, self.home_z) + + def _set_home_pos(self, home_pos): + if home_pos: + self.home_x, self.home_y, self.home_z = home_pos + home_pos = property(_get_home_pos, _set_home_pos) + + def _get_layers_count(self): + return len(self.all_zs) + layers_count = property(_get_layers_count) + + def __init__(self, data = None, home_pos = None, + layer_callback = None, deferred = False): + if not deferred: + self.prepare(data, home_pos, layer_callback) + + def prepare(self, data = None, home_pos = None, layer_callback = None): + self.home_pos = home_pos + if data: + line_class = self.line_class + self.lines = [line_class(l2) for l2 in + (l.strip() for l in data) + if l2] + self._preprocess(build_layers = True, + layer_callback = layer_callback) + else: + self.lines = [] + self.append_layer_id = 0 + self.append_layer = Layer([]) + self.all_layers = [self.append_layer] + self.all_zs = set() + self.layers = {} + self.layer_idxs = array('I', []) + self.line_idxs = array('I', []) + + def __len__(self): + return len(self.line_idxs) + + def __iter__(self): + return self.lines.__iter__() + + def prepend_to_layer(self, commands, layer_idx): + # Prepend commands in reverse order + commands = [c.strip() for c in commands[::-1] if c.strip()] + layer = self.all_layers[layer_idx] + # Find start index to append lines + # and end index to append new indices + start_index = self.layer_idxs.index(layer_idx) + for i in range(start_index, len(self.layer_idxs)): + if self.layer_idxs[i] != layer_idx: + end_index = i + break + else: + end_index = i + 1 + end_line = self.line_idxs[end_index - 1] + for i, command in enumerate(commands): + gline = Line(command) + # Split to get command + split(gline) + # Force is_move to False + gline.is_move = False + # Insert gline at beginning of layer + layer.insert(0, gline) + # Insert gline at beginning of list + self.lines.insert(start_index, gline) + # Update indices arrays & global gcodes list + self.layer_idxs.insert(end_index + i, layer_idx) + self.line_idxs.insert(end_index + i, end_line + i + 1) + return commands[::-1] + + def rewrite_layer(self, commands, layer_idx): + # Prepend commands in reverse order + commands = [c.strip() for c in commands[::-1] if c.strip()] + layer = self.all_layers[layer_idx] + # Find start index to append lines + # and end index to append new indices + start_index = self.layer_idxs.index(layer_idx) + for i in range(start_index, len(self.layer_idxs)): + if self.layer_idxs[i] != layer_idx: + end_index = i + break + else: + end_index = i + 1 + self.layer_idxs = self.layer_idxs[:start_index] + array('I', len(commands) * [layer_idx]) + self.layer_idxs[end_index:] + self.line_idxs = self.line_idxs[:start_index] + array('I', range(len(commands))) + self.line_idxs[end_index:] + del self.lines[start_index:end_index] + del layer[:] + for i, command in enumerate(commands): + gline = Line(command) + # Split to get command + split(gline) + # Force is_move to False + gline.is_move = False + # Insert gline at beginning of layer + layer.insert(0, gline) + # Insert gline at beginning of list + self.lines.insert(start_index, gline) + return commands[::-1] + + def append(self, command, store = True): + command = command.strip() + if not command: + return + gline = Line(command) + self._preprocess([gline]) + if store: + self.lines.append(gline) + self.append_layer.append(gline) + self.layer_idxs.append(self.append_layer_id) + self.line_idxs.append(len(self.append_layer)) + return gline + + def _preprocess(self, lines = None, build_layers = False, + layer_callback = None): + """Checks for imperial/relativeness settings and tool changes""" + if not lines: + lines = self.lines + imperial = self.imperial + relative = self.relative + relative_e = self.relative_e + current_tool = self.current_tool + current_x = self.current_x + current_y = self.current_y + current_z = self.current_z + offset_x = self.offset_x + offset_y = self.offset_y + offset_z = self.offset_z + + # Extrusion computation + current_e = self.current_e + offset_e = self.offset_e + total_e = self.total_e + max_e = self.max_e + + current_e_multi = self.current_e_multi[current_tool] + offset_e_multi = self.offset_e_multi[current_tool] + total_e_multi = self.total_e_multi[current_tool] + max_e_multi = self.max_e_multi[current_tool] + + # Store this one out of the build_layers scope for efficiency + cur_layer_has_extrusion = False + + # Initialize layers and other global computations + if build_layers: + # Bounding box computation + xmin = float("inf") + ymin = float("inf") + zmin = 0 + xmax = float("-inf") + ymax = float("-inf") + zmax = float("-inf") + # Also compute extrusion-only values + xmin_e = float("inf") + ymin_e = float("inf") + xmax_e = float("-inf") + ymax_e = float("-inf") + + # Duration estimation + # TODO: + # get device caps from firmware: max speed, acceleration/axis + # (including extruder) + # calculate the maximum move duration accounting for above ;) + lastx = lasty = lastz = laste = lastf = 0.0 + lastdx = 0 + lastdy = 0 + x = y = e = f = 0.0 + currenttravel = 0.0 + moveduration = 0.0 + totalduration = 0.0 + acceleration = 2000.0 # mm/s^2 + layerbeginduration = 0.0 + + # Initialize layers + all_layers = self.all_layers = [] + all_zs = self.all_zs = set() + layer_idxs = self.layer_idxs = [] + line_idxs = self.line_idxs = [] + + layer_id = 0 + layer_line = 0 + + last_layer_z = None + prev_z = None + prev_base_z = (None, None) + cur_z = None + cur_lines = [] + + if self.line_class != Line: + get_line = lambda l: Line(l.raw) + else: + get_line = lambda l: l + for true_line in lines: + # # Parse line + # Use a heavy copy of the light line to preprocess + line = get_line(true_line) + split_raw = split(line) + if line.command: + # Update properties + if line.is_move: + line.relative = relative + line.relative_e = relative_e + line.current_tool = current_tool + elif line.command == "G20": + imperial = True + elif line.command == "G21": + imperial = False + elif line.command == "G90": + relative = False + relative_e = False + elif line.command == "G91": + relative = True + relative_e = True + elif line.command == "M82": + relative_e = False + elif line.command == "M83": + relative_e = True + elif line.command[0] == "T": + current_tool = int(line.command[1:]) + while(current_tool+1>len(self.current_e_multi)): + self.current_e_multi+=[0] + self.offset_e_multi+=[0] + self.total_e_multi+=[0] + self.max_e_multi+=[0] + current_e_multi = self.current_e_multi[current_tool] + offset_e_multi = self.offset_e_multi[current_tool] + total_e_multi = self.total_e_multi[current_tool] + max_e_multi = self.max_e_multi[current_tool] + + + if line.command[0] == "G": + parse_coordinates(line, split_raw, imperial) + + # Compute current position + if line.is_move: + x = line.x + y = line.y + z = line.z + + if line.f is not None: + self.current_f = line.f + + if line.relative: + x = current_x + (x or 0) + y = current_y + (y or 0) + z = current_z + (z or 0) + else: + if x is not None: x = x + offset_x + if y is not None: y = y + offset_y + if z is not None: z = z + offset_z + + if x is not None: current_x = x + if y is not None: current_y = y + if z is not None: current_z = z + + elif line.command == "G28": + home_all = not any([line.x, line.y, line.z]) + if home_all or line.x is not None: + offset_x = 0 + current_x = self.home_x + if home_all or line.y is not None: + offset_y = 0 + current_y = self.home_y + if home_all or line.z is not None: + offset_z = 0 + current_z = self.home_z + + elif line.command == "G92": + if line.x is not None: offset_x = current_x - line.x + if line.y is not None: offset_y = current_y - line.y + if line.z is not None: offset_z = current_z - line.z + + line.current_x = current_x + line.current_y = current_y + line.current_z = current_z + + # # Process extrusion + if line.e is not None: + if line.is_move: + if line.relative_e: + line.extruding = line.e > 0 + total_e += line.e + current_e += line.e + total_e_multi += line.e + current_e_multi += line.e + else: + new_e = line.e + offset_e + line.extruding = new_e > current_e + total_e += new_e - current_e + current_e = new_e + new_e_multi = line.e + offset_e_multi + total_e_multi += new_e_multi - current_e_multi + current_e_multi = new_e_multi + + max_e = max(max_e, total_e) + max_e_multi=max(max_e_multi, total_e_multi) + cur_layer_has_extrusion |= line.extruding + elif line.command == "G92": + offset_e = current_e - line.e + offset_e_multi = current_e_multi - line.e + + self.current_e_multi[current_tool]=current_e_multi + self.offset_e_multi[current_tool]=offset_e_multi + self.max_e_multi[current_tool]=max_e_multi + self.total_e_multi[current_tool]=total_e_multi + + # # Create layers and perform global computations + if build_layers: + # Update bounding box + if line.is_move: + if line.extruding: + if line.current_x is not None: + xmin_e = min(xmin_e, line.current_x) + xmax_e = max(xmax_e, line.current_x) + if line.current_y is not None: + ymin_e = min(ymin_e, line.current_y) + ymax_e = max(ymax_e, line.current_y) + if max_e <= 0: + if line.current_x is not None: + xmin = min(xmin, line.current_x) + xmax = max(xmax, line.current_x) + if line.current_y is not None: + ymin = min(ymin, line.current_y) + ymax = max(ymax, line.current_y) + + # Compute duration + if line.command == "G0" or line.command == "G1": + x = line.x if line.x is not None else lastx + y = line.y if line.y is not None else lasty + z = line.z if line.z is not None else lastz + e = line.e if line.e is not None else laste + # mm/s vs mm/m => divide by 60 + f = line.f / 60.0 if line.f is not None else lastf + + # given last feedrate and current feedrate calculate the + # distance needed to achieve current feedrate. + # if travel is longer than req'd distance, then subtract + # distance to achieve full speed, and add the time it took + # to get there. + # then calculate the time taken to complete the remaining + # distance + + # FIXME: this code has been proven to be super wrong when 2 + # subsquent moves are in opposite directions, as requested + # speed is constant but printer has to fully decellerate + # and reaccelerate + # The following code tries to fix it by forcing a full + # reacceleration if this move is in the opposite direction + # of the previous one + dx = x - lastx + dy = y - lasty + if dx * lastdx + dy * lastdy <= 0: + lastf = 0 + + currenttravel = math.hypot(dx, dy) + if currenttravel == 0: + if line.z is not None: + currenttravel = abs(line.z) if line.relative else abs(line.z - lastz) + elif line.e is not None: + currenttravel = abs(line.e) if line.relative_e else abs(line.e - laste) + # Feedrate hasn't changed, no acceleration/decceleration planned + if f == lastf: + moveduration = currenttravel / f if f != 0 else 0. + else: + # FIXME: review this better + # this looks wrong : there's little chance that the feedrate we'll decelerate to is the previous feedrate + # shouldn't we instead look at three consecutive moves ? + distance = 2 * abs(((lastf + f) * (f - lastf) * 0.5) / acceleration) # multiply by 2 because we have to accelerate and decelerate + if distance <= currenttravel and lastf + f != 0 and f != 0: + moveduration = 2 * distance / (lastf + f) # This is distance / mean(lastf, f) + moveduration += (currenttravel - distance) / f + else: + moveduration = 2 * currenttravel / (lastf + f) # This is currenttravel / mean(lastf, f) + # FIXME: probably a little bit optimistic, but probably a much better estimate than the previous one: + # moveduration = math.sqrt(2 * distance / acceleration) # probably buggy : not taking actual travel into account + + lastdx = dx + lastdy = dy + + totalduration += moveduration + + lastx = x + lasty = y + lastz = z + laste = e + lastf = f + elif line.command == "G4": + moveduration = P(line) + if moveduration: + moveduration /= 1000.0 + totalduration += moveduration + + # FIXME : looks like this needs to be tested with "lift Z on move" + if line.z is not None: + if line.command == "G92": + cur_z = line.z + elif line.is_move: + if line.relative and cur_z is not None: + cur_z += line.z + else: + cur_z = line.z + + # FIXME: the logic behind this code seems to work, but it might be + # broken + if cur_z != prev_z: + if prev_z is not None and last_layer_z is not None: + offset = self.est_layer_height if self.est_layer_height else 0.01 + if abs(prev_z - last_layer_z) < offset: + if self.est_layer_height is None: + zs = sorted([l.z for l in all_layers if l.z is not None]) + heights = [round(zs[i + 1] - zs[i], 3) for i in range(len(zs) - 1)] + heights = [height for height in heights if height] + if len(heights) >= 2: self.est_layer_height = heights[1] + elif heights: self.est_layer_height = heights[0] + else: self.est_layer_height = 0.1 + base_z = round(prev_z - (prev_z % self.est_layer_height), 2) + else: + base_z = round(prev_z, 2) + else: + base_z = prev_z + + if base_z != prev_base_z: + new_layer = Layer(cur_lines, base_z) + new_layer.duration = totalduration - layerbeginduration + layerbeginduration = totalduration + all_layers.append(new_layer) + if cur_layer_has_extrusion and prev_z not in all_zs: + all_zs.add(prev_z) + cur_lines = [] + cur_layer_has_extrusion = False + layer_id += 1 + layer_line = 0 + last_layer_z = base_z + if layer_callback is not None: + layer_callback(self, len(all_layers) - 1) + + prev_base_z = base_z + + if build_layers: + cur_lines.append(true_line) + layer_idxs.append(layer_id) + line_idxs.append(layer_line) + layer_line += 1 + prev_z = cur_z + # ## Loop done + + # Store current status + self.imperial = imperial + self.relative = relative + self.relative_e = relative_e + self.current_tool = current_tool + self.current_x = current_x + self.current_y = current_y + self.current_z = current_z + self.offset_x = offset_x + self.offset_y = offset_y + self.offset_z = offset_z + self.current_e = current_e + self.offset_e = offset_e + self.max_e = max_e + self.total_e = total_e + self.current_e_multi[current_tool]=current_e_multi + self.offset_e_multi[current_tool]=offset_e_multi + self.max_e_multi[current_tool]=max_e_multi + self.total_e_multi[current_tool]=total_e_multi + + + # Finalize layers + if build_layers: + if cur_lines: + new_layer = Layer(cur_lines, prev_z) + new_layer.duration = totalduration - layerbeginduration + layerbeginduration = totalduration + all_layers.append(new_layer) + if cur_layer_has_extrusion and prev_z not in all_zs: + all_zs.add(prev_z) + + self.append_layer_id = len(all_layers) + self.append_layer = Layer([]) + self.append_layer.duration = 0 + all_layers.append(self.append_layer) + self.layer_idxs = array('I', layer_idxs) + self.line_idxs = array('I', line_idxs) + + # Compute bounding box + all_zs = self.all_zs.union(set([zmin])).difference(set([None])) + zmin = min(all_zs) + zmax = max(all_zs) + + self.filament_length = self.max_e + while len(self.filament_length_multi) 0: + self.xmin = xmin_e if not math.isinf(xmin_e) else 0 + self.xmax = xmax_e if not math.isinf(xmax_e) else 0 + self.ymin = ymin_e if not math.isinf(ymin_e) else 0 + self.ymax = ymax_e if not math.isinf(ymax_e) else 0 + else: + self.xmin = xmin if not math.isinf(xmin) else 0 + self.xmax = xmax if not math.isinf(xmax) else 0 + self.ymin = ymin if not math.isinf(ymin) else 0 + self.ymax = ymax if not math.isinf(ymax) else 0 + self.zmin = zmin if not math.isinf(zmin) else 0 + self.zmax = zmax if not math.isinf(zmax) else 0 + self.width = self.xmax - self.xmin + self.depth = self.ymax - self.ymin + self.height = self.zmax - self.zmin + + # Finalize duration + totaltime = datetime.timedelta(seconds = int(totalduration)) + self.duration = totaltime + + def idxs(self, i): + return self.layer_idxs[i], self.line_idxs[i] + + def estimate_duration(self): + return self.layers_count, self.duration + +class LightGCode(GCode): + line_class = LightLine + +def main(): + if len(sys.argv) < 2: + print "usage: %s filename.gcode" % sys.argv[0] + return + + print "Line object size:", sys.getsizeof(Line("G0 X0")) + print "Light line object size:", sys.getsizeof(LightLine("G0 X0")) + gcode = GCode(open(sys.argv[1], "rU")) + + print "Dimensions:" + xdims = (gcode.xmin, gcode.xmax, gcode.width) + print "\tX: %0.02f - %0.02f (%0.02f)" % xdims + ydims = (gcode.ymin, gcode.ymax, gcode.depth) + print "\tY: %0.02f - %0.02f (%0.02f)" % ydims + zdims = (gcode.zmin, gcode.zmax, gcode.height) + print "\tZ: %0.02f - %0.02f (%0.02f)" % zdims + print "Filament used: %0.02fmm" % gcode.filament_length + for i in enumerate(gcode.filament_length_multi): + print "E%d %0.02fmm" % (i[0],i[1]) + print "Number of layers: %d" % gcode.layers_count + print "Estimated duration: %s" % gcode.estimate_duration()[1] + +if __name__ == '__main__': + main() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gcoder_heapy_support.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gcoder_heapy_support.patch Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,15 @@ +--- printrun/gcoder_line.c 2013-06-15 16:08:53.260081109 +0200 ++++ printrun/gcoder_line.c 2013-06-15 16:08:57.083439793 +0200 +@@ -3945,2 +3945,4 @@ static int __Pyx_InitGlobals(void) { + ++#include "gcoder_line_extra.h" ++ + #if PY_MAJOR_VERSION < 3 +@@ -4032,2 +4034,7 @@ PyMODINIT_FUNC PyInit_gcoder_line(void) + /*--- Execution code ---*/ ++ nysets_heapdefs[0].type = &__pyx_type_8printrun_11gcoder_line_GLine; ++ if (PyDict_SetItemString(__pyx_d, ++ "_NyHeapDefs_", ++ PyCObject_FromVoidPtrAndDesc(&nysets_heapdefs, "NyHeapDef[] v1.0", 0)) < 0) ++{__pyx_filename = __pyx_f[0]; __pyx_lineno = 61; __pyx_clineno = __LINE__; goto __pyx_L1_error;} + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gcoder_line.pyx --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gcoder_line.pyx Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,256 @@ +# This file is copied from GCoder. +# +# GCoder is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GCoder is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +from libc.stdlib cimport malloc, free +from libc.stdint cimport uint8_t, uint32_t +from libc.string cimport strlen, strncpy + +cdef char* copy_string(object value): + cdef char* orig = value + str_len = len(orig) + cdef char* array = malloc(str_len + 1) + strncpy(array, orig, str_len) + array[str_len] = 0; + return array + +cdef enum BitPos: + pos_raw = 1 << 0 + pos_command = 1 << 1 + pos_is_move = 1 << 2 + pos_x = 1 << 3 + pos_y = 1 << 4 + pos_z = 1 << 5 + pos_e = 1 << 6 + pos_f = 1 << 7 + pos_i = 1 << 8 + pos_j = 1 << 9 + pos_relative = 1 << 10 + pos_relative_e = 1 << 11 + pos_extruding = 1 << 12 + pos_current_x = 1 << 13 + pos_current_y = 1 << 14 + pos_current_z = 1 << 15 + pos_current_tool = 1 << 16 + pos_gcview_end_vertex = 1 << 17 + # WARNING: don't use bits 24 to 31 as we store current_tool there + +cdef inline uint32_t has_var(uint32_t status, uint32_t pos): + return status & pos + +cdef inline uint32_t set_has_var(uint32_t status, uint32_t pos): + return status | pos + +cdef inline uint32_t unset_has_var(uint32_t status, uint32_t pos): + return status & ~pos + +cdef class GLine: + + cdef char* _raw + cdef char* _command + cdef float _x, _y, _z, _e, _f, _i, _j + cdef float _current_x, _current_y, _current_z + cdef uint32_t _gcview_end_vertex + cdef uint32_t _status + + __slots__ = () + + def __cinit__(self): + self._status = 0 + self._raw = NULL + self._command = NULL + + def __init__(self, line): + self.raw = line + + def __dealloc__(self): + if self._raw != NULL: free(self._raw) + if self._command != NULL: free(self._command) + + property x: + def __get__(self): + if has_var(self._status, pos_x): return self._x + else: return None + def __set__(self, value): + self._x = value + self._status = set_has_var(self._status, pos_x) + property y: + def __get__(self): + if has_var(self._status, pos_y): return self._y + else: return None + def __set__(self, value): + self._y = value + self._status = set_has_var(self._status, pos_y) + property z: + def __get__(self): + if has_var(self._status, pos_z): return self._z + else: return None + def __set__(self, value): + self._z = value + self._status = set_has_var(self._status, pos_z) + property e: + def __get__(self): + if has_var(self._status, pos_e): return self._e + else: return None + def __set__(self, value): + self._e = value + self._status = set_has_var(self._status, pos_e) + property f: + def __get__(self): + if has_var(self._status, pos_f): return self._f + else: return None + def __set__(self, value): + self._f = value + self._status = set_has_var(self._status, pos_f) + property i: + def __get__(self): + if has_var(self._status, pos_i): return self._i + else: return None + def __set__(self, value): + self._i = value + self._status = set_has_var(self._status, pos_i) + property j: + def __get__(self): + if has_var(self._status, pos_j): return self._j + else: return None + def __set__(self, value): + self._j = value + self._status = set_has_var(self._status, pos_j) + property is_move: + def __get__(self): + if has_var(self._status, pos_is_move): return True + else: return False + def __set__(self, value): + if value: self._status = set_has_var(self._status, pos_is_move) + else: self._status = unset_has_var(self._status, pos_is_move) + property relative: + def __get__(self): + if has_var(self._status, pos_relative): return True + else: return False + def __set__(self, value): + if value: self._status = set_has_var(self._status, pos_relative) + else: self._status = unset_has_var(self._status, pos_relative) + property relative_e: + def __get__(self): + if has_var(self._status, pos_relative_e): return True + else: return False + def __set__(self, value): + if value: self._status = set_has_var(self._status, pos_relative_e) + else: self._status = unset_has_var(self._status, pos_relative_e) + property extruding: + def __get__(self): + if has_var(self._status, pos_extruding): return True + else: return False + def __set__(self, value): + if value: self._status = set_has_var(self._status, pos_extruding) + else: self._status = unset_has_var(self._status, pos_extruding) + property current_x: + def __get__(self): + if has_var(self._status, pos_current_x): return self._current_x + else: return None + def __set__(self, value): + self._current_x = value + self._status = set_has_var(self._status, pos_current_x) + property current_y: + def __get__(self): + if has_var(self._status, pos_current_y): return self._current_y + else: return None + def __set__(self, value): + self._current_y = value + self._status = set_has_var(self._status, pos_current_y) + property current_z: + def __get__(self): + if has_var(self._status, pos_current_z): return self._current_z + else: return None + def __set__(self, value): + self._current_z = value + self._status = set_has_var(self._status, pos_current_z) + property current_tool: + def __get__(self): + if has_var(self._status, pos_current_tool): return self._status >> 24 + else: return None + def __set__(self, value): + self._status = (self._status & ((1 << 24) - 1)) | (value << 24) + self._status = set_has_var(self._status, pos_current_tool) + property gcview_end_vertex: + def __get__(self): + if has_var(self._status, pos_gcview_end_vertex): return self._gcview_end_vertex + else: return None + def __set__(self, value): + self._gcview_end_vertex = value + self._status = set_has_var(self._status, pos_gcview_end_vertex) + property raw: + def __get__(self): + if has_var(self._status, pos_raw): return self._raw + else: return None + def __set__(self, value): + # WARNING: memory leak could happen here, as we don't do the following : + # if self._raw != NULL: free(self._raw) + self._raw = copy_string(value) + self._status = set_has_var(self._status, pos_raw) + property command: + def __get__(self): + if has_var(self._status, pos_command): return self._command + else: return None + def __set__(self, value): + # WARNING: memory leak could happen here, as we don't do the following : + # if self._command != NULL: free(self._command) + self._command = copy_string(value) + self._status = set_has_var(self._status, pos_command) + +cdef class GLightLine: + + cdef char* _raw + cdef char* _command + cdef uint8_t _status + + __slots__ = () + + def __cinit__(self): + self._status = 0 + self._raw = NULL + self._command = NULL + + def __init__(self, line): + self.raw = line + + def __dealloc__(self): + if self._raw != NULL: free(self._raw) + if self._command != NULL: free(self._command) + + property raw: + def __get__(self): + if has_var(self._status, pos_raw): return self._raw + else: return None + def __set__(self, value): + # WARNING: memory leak could happen here, as we don't do the following : + # if self._raw != NULL: free(self._raw) + self._raw = copy_string(value) + self._status = set_has_var(self._status, pos_raw) + property command: + def __get__(self): + if has_var(self._status, pos_command): return self._command + else: return None + def __set__(self, value): + # WARNING: memory leak could happen here, as we don't do the following : + # if self._command != NULL: free(self._command) + self._command = copy_string(value) + self._status = set_has_var(self._status, pos_command) + property is_move: + def __get__(self): + if has_var(self._status, pos_is_move): return True + else: return False + def __set__(self, value): + if value: self._status = set_has_var(self._status, pos_is_move) + else: self._status = unset_has_var(self._status, pos_is_move) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gcoder_line_extra.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gcoder_line_extra.h Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,30 @@ +typedef int (*NyHeapDef_SizeGetter) (PyObject *obj); +typedef struct { + int flags; /* As yet, only 0 */ + PyTypeObject *type; /* The type it regards */ + NyHeapDef_SizeGetter size; + void *traverse; + void *relate; + void *resv3, *resv4, *resv5; /* Reserved for future bin. comp. */ +} NyHeapDef; + +int gline_size(struct __pyx_obj_8printrun_11gcoder_line_GLine *gline) { + int size = __pyx_type_8printrun_11gcoder_line_GLine.tp_basicsize; + if (gline->_raw != NULL) + size += strlen(gline->_raw) + 1; + if (gline->_command != NULL) + size += strlen(gline->_command) + 1; + return size; +} + +static NyHeapDef nysets_heapdefs[] = { + {0, 0, (NyHeapDef_SizeGetter) gline_size}, +}; + +/* + nysets_heapdefs[0].type = &__pyx_type_8printrun_11gcoder_line_GLine; + if (PyDict_SetItemString(__pyx_d, + "_NyHeapDefs_", + PyCObject_FromVoidPtrAndDesc(&nysets_heapdefs, "NyHeapDef[] v1.0", 0)) < 0) +{__pyx_filename = __pyx_f[0]; __pyx_lineno = 61; __pyx_clineno = __LINE__; goto __pyx_L1_error;} +*/ diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gcview.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gcview.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,522 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import logging +import wx + +from . import gcoder +from .gl.panel import wxGLPanel +from .gl.trackball import build_rotmatrix +from .gl.libtatlin import actors +from .injectgcode import injector, injector_edit + +from pyglet.gl import glPushMatrix, glPopMatrix, \ + glTranslatef, glRotatef, glScalef, glMultMatrixd, \ + glGetDoublev, GL_MODELVIEW_MATRIX, GLdouble + +from .gviz import GvizBaseFrame + +from .utils import imagefile, install_locale, get_home_pos +install_locale('pronterface') + +def create_model(light): + if light: + return actors.GcodeModelLight() + else: + return actors.GcodeModel() + +def gcode_dims(g): + return ((g.xmin, g.xmax, g.width), + (g.ymin, g.ymax, g.depth), + (g.zmin, g.zmax, g.height)) + +def set_model_colors(model, root): + for field in dir(model): + if field.startswith("color_"): + root_fieldname = "gcview_" + field + if hasattr(root, root_fieldname): + setattr(model, field, getattr(root, root_fieldname)) + +def recreate_platform(self, build_dimensions, circular): + self.platform = actors.Platform(build_dimensions, circular = circular) + self.objects[0].model = self.platform + wx.CallAfter(self.Refresh) + +def set_gcview_params(self, path_width, path_height): + self.path_halfwidth = path_width / 2 + self.path_halfheight = path_height / 2 + has_changed = False + for obj in self.objects[1:]: + if isinstance(obj.model, actors.GcodeModel): + obj.model.set_path_size(self.path_halfwidth, self.path_halfheight) + has_changed = True + return has_changed + +class GcodeViewPanel(wxGLPanel): + + def __init__(self, parent, id = wx.ID_ANY, + build_dimensions = None, realparent = None, + antialias_samples = 0): + super(GcodeViewPanel, self).__init__(parent, id, wx.DefaultPosition, + wx.DefaultSize, 0, + antialias_samples = antialias_samples) + self.canvas.Bind(wx.EVT_MOUSE_EVENTS, self.move) + self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.double) + self.canvas.Bind(wx.EVT_KEY_DOWN, self.keypress) + self.initialized = 0 + self.canvas.Bind(wx.EVT_MOUSEWHEEL, self.wheel) + self.parent = realparent if realparent else parent + self.initpos = None + if build_dimensions: + self.build_dimensions = build_dimensions + else: + self.build_dimensions = [200, 200, 100, 0, 0, 0] + self.dist = max(self.build_dimensions[0], self.build_dimensions[1]) + self.basequat = [0, 0, 0, 1] + self.mousepos = [0, 0] + + def inject(self): + l = self.parent.model.num_layers_to_draw + filtered = [k for k, v in self.parent.model.layer_idxs_map.iteritems() if v == l] + if filtered: + injector(self.parent.model.gcode, l, filtered[0]) + else: + logging.error(_("Invalid layer for injection")) + + def editlayer(self): + l = self.parent.model.num_layers_to_draw + filtered = [k for k, v in self.parent.model.layer_idxs_map.iteritems() if v == l] + if filtered: + injector_edit(self.parent.model.gcode, l, filtered[0]) + else: + logging.error(_("Invalid layer for edition")) + + def setlayercb(self, layer): + pass + + def OnInitGL(self, *args, **kwargs): + super(GcodeViewPanel, self).OnInitGL(*args, **kwargs) + if hasattr(self.parent, "filenames") and self.parent.filenames: + for filename in self.parent.filenames: + self.parent.load_file(filename) + self.parent.autoplate() + if hasattr(self.parent, "loadcb"): + self.parent.loadcb() + self.parent.filenames = None + + def create_objects(self): + '''create opengl objects when opengl is initialized''' + for obj in self.parent.objects: + if obj.model and obj.model.loaded and not obj.model.initialized: + obj.model.init() + + def update_object_resize(self): + '''called when the window recieves only if opengl is initialized''' + pass + + def draw_objects(self): + '''called in the middle of ondraw after the buffer has been cleared''' + self.create_objects() + + glPushMatrix() + # Rotate according to trackball + glMultMatrixd(build_rotmatrix(self.basequat)) + # Move origin to bottom left of platform + platformx0 = -self.build_dimensions[3] - self.parent.platform.width / 2 + platformy0 = -self.build_dimensions[4] - self.parent.platform.depth / 2 + glTranslatef(platformx0, platformy0, 0) + + for obj in self.parent.objects: + if not obj.model \ + or not obj.model.loaded \ + or not obj.model.initialized: + continue + glPushMatrix() + glTranslatef(*(obj.offsets)) + glRotatef(obj.rot, 0.0, 0.0, 1.0) + glTranslatef(*(obj.centeroffset)) + glScalef(*obj.scale) + + obj.model.display() + glPopMatrix() + glPopMatrix() + + # ========================================================================== + # Utils + # ========================================================================== + def get_modelview_mat(self, local_transform): + mvmat = (GLdouble * 16)() + if local_transform: + glPushMatrix() + # Rotate according to trackball + glMultMatrixd(build_rotmatrix(self.basequat)) + # Move origin to bottom left of platform + platformx0 = -self.build_dimensions[3] - self.parent.platform.width / 2 + platformy0 = -self.build_dimensions[4] - self.parent.platform.depth / 2 + glTranslatef(platformx0, platformy0, 0) + glGetDoublev(GL_MODELVIEW_MATRIX, mvmat) + glPopMatrix() + else: + glGetDoublev(GL_MODELVIEW_MATRIX, mvmat) + return mvmat + + def double(self, event): + if hasattr(self.parent, "clickcb") and self.parent.clickcb: + self.parent.clickcb(event) + + def move(self, event): + """react to mouse actions: + no mouse: show red mousedrop + LMB: rotate viewport + RMB: move viewport + """ + if event.Entering(): + self.canvas.SetFocus() + event.Skip() + return + if event.Dragging() and event.LeftIsDown(): + self.handle_rotation(event) + elif event.Dragging() and event.RightIsDown(): + self.handle_translation(event) + elif event.LeftUp(): + self.initpos = None + elif event.RightUp(): + self.initpos = None + else: + event.Skip() + return + event.Skip() + wx.CallAfter(self.Refresh) + + def layerup(self): + if not hasattr(self.parent, "model") or not self.parent.model: + return + max_layers = self.parent.model.max_layers + current_layer = self.parent.model.num_layers_to_draw + # accept going up to max_layers + 1 + # max_layers means visualizing the last layer differently, + # max_layers + 1 means visualizing all layers with the same color + new_layer = min(max_layers + 1, current_layer + 1) + self.parent.model.num_layers_to_draw = new_layer + self.parent.setlayercb(new_layer) + wx.CallAfter(self.Refresh) + + def layerdown(self): + if not hasattr(self.parent, "model") or not self.parent.model: + return + current_layer = self.parent.model.num_layers_to_draw + new_layer = max(1, current_layer - 1) + self.parent.model.num_layers_to_draw = new_layer + self.parent.setlayercb(new_layer) + wx.CallAfter(self.Refresh) + + def handle_wheel(self, event): + delta = event.GetWheelRotation() + factor = 1.05 + if event.ControlDown(): + factor = 1.02 + if hasattr(self.parent, "model") and event.ShiftDown(): + if not self.parent.model: + return + count = 1 if not event.ControlDown() else 10 + for i in range(count): + if delta > 0: self.layerup() + else: self.layerdown() + return + x, y = event.GetPositionTuple() + x, y, _ = self.mouse_to_3d(x, y) + if delta > 0: + self.zoom(factor, (x, y)) + else: + self.zoom(1 / factor, (x, y)) + + def wheel(self, event): + """react to mouse wheel actions: + without shift: set max layer + with shift: zoom viewport + """ + self.handle_wheel(event) + wx.CallAfter(self.Refresh) + + def fit(self): + if not self.parent.model or not self.parent.model.loaded: + return + self.canvas.SetCurrent(self.context) + dims = gcode_dims(self.parent.model.gcode) + self.reset_mview(1.0) + center_x = (dims[0][0] + dims[0][1]) / 2 + center_y = (dims[1][0] + dims[1][1]) / 2 + center_x = self.build_dimensions[0] / 2 - center_x + center_y = self.build_dimensions[1] / 2 - center_y + if self.orthographic: + ratio = float(self.dist) / max(dims[0][2], dims[1][2]) + glScalef(ratio, ratio, 1) + glTranslatef(center_x, center_y, 0) + wx.CallAfter(self.Refresh) + + def keypress(self, event): + """gets keypress events and moves/rotates acive shape""" + step = 1.1 + if event.ControlDown(): + step = 1.05 + kup = [85, 315] # Up keys + kdo = [68, 317] # Down Keys + kzi = [wx.WXK_PAGEDOWN, 388, 316, 61] # Zoom In Keys + kzo = [wx.WXK_PAGEUP, 390, 314, 45] # Zoom Out Keys + kfit = [70] # Fit to print keys + kshowcurrent = [67] # Show only current layer keys + kreset = [82] # Reset keys + key = event.GetKeyCode() + if key in kup: + self.layerup() + if key in kdo: + self.layerdown() + x, y, _ = self.mouse_to_3d(self.width / 2, self.height / 2) + if key in kzi: + self.zoom_to_center(step) + if key in kzo: + self.zoom_to_center(1 / step) + if key in kfit: + self.fit() + if key in kshowcurrent: + if not self.parent.model or not self.parent.model.loaded: + return + self.parent.model.only_current = not self.parent.model.only_current + wx.CallAfter(self.Refresh) + if key in kreset: + self.resetview() + event.Skip() + + def resetview(self): + self.canvas.SetCurrent(self.context) + self.reset_mview(0.9) + self.basequat = [0, 0, 0, 1] + wx.CallAfter(self.Refresh) + +class GCObject(object): + + def __init__(self, model): + self.offsets = [0, 0, 0] + self.centeroffset = [0, 0, 0] + self.rot = 0 + self.curlayer = 0.0 + self.scale = [1.0, 1.0, 1.0] + self.model = model + +class GcodeViewLoader(object): + + path_halfwidth = 0.2 + path_halfheight = 0.15 + + def addfile_perlayer(self, gcode = None, showall = False): + self.model = create_model(self.root.settings.light3d + if self.root else False) + if isinstance(self.model, actors.GcodeModel): + self.model.set_path_size(self.path_halfwidth, self.path_halfheight) + self.objects[-1].model = self.model + if self.root: + set_model_colors(self.model, self.root) + if gcode is not None: + generator = self.model.load_data(gcode) + generator_output = generator.next() + while generator_output is not None: + yield generator_output + generator_output = generator.next() + wx.CallAfter(self.Refresh) + yield None + + def addfile(self, gcode = None, showall = False): + generator = self.addfile_perlayer(gcode, showall) + while generator.next() is not None: + continue + + def set_gcview_params(self, path_width, path_height): + return set_gcview_params(self, path_width, path_height) + +class GcodeViewMainWrapper(GcodeViewLoader): + + def __init__(self, parent, build_dimensions, root, circular, antialias_samples): + self.root = root + self.glpanel = GcodeViewPanel(parent, realparent = self, + build_dimensions = build_dimensions, + antialias_samples = antialias_samples) + self.glpanel.SetMinSize((150, 150)) + if self.root and hasattr(self.root, "gcview_color_background"): + self.glpanel.color_background = self.root.gcview_color_background + self.clickcb = None + self.widget = self.glpanel + self.refresh_timer = wx.CallLater(100, self.Refresh) + self.p = self # Hack for backwards compatibility with gviz API + self.platform = actors.Platform(build_dimensions, circular = circular) + self.model = None + self.objects = [GCObject(self.platform), GCObject(None)] + + def __getattr__(self, name): + return getattr(self.glpanel, name) + + def set_current_gline(self, gline): + if gline.is_move and gline.gcview_end_vertex is not None \ + and self.model and self.model.loaded: + self.model.printed_until = gline.gcview_end_vertex + if not self.refresh_timer.IsRunning(): + self.refresh_timer.Start() + + def recreate_platform(self, build_dimensions, circular): + return recreate_platform(self, build_dimensions, circular) + + def addgcodehighlight(self, *a): + pass + + def setlayer(self, layer): + if layer in self.model.layer_idxs_map: + viz_layer = self.model.layer_idxs_map[layer] + self.parent.model.num_layers_to_draw = viz_layer + wx.CallAfter(self.Refresh) + + def clear(self): + self.model = None + self.objects[-1].model = None + wx.CallAfter(self.Refresh) + +class GcodeViewFrame(GvizBaseFrame, GcodeViewLoader): + '''A simple class for using OpenGL with wxPython.''' + + def __init__(self, parent, ID, title, build_dimensions, objects = None, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, root = None, circular = False, + antialias_samples = 0): + GvizBaseFrame.__init__(self, parent, ID, title, + pos, size, style) + self.root = root + + panel, vbox = self.create_base_ui() + + self.refresh_timer = wx.CallLater(100, self.Refresh) + self.p = self # Hack for backwards compatibility with gviz API + self.clonefrom = objects + self.platform = actors.Platform(build_dimensions, circular = circular) + if objects: + self.model = objects[1].model + else: + self.model = None + self.objects = [GCObject(self.platform), GCObject(None)] + + fit_image = wx.Image(imagefile('fit.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap() + self.toolbar.InsertLabelTool(6, 8, " " + _("Fit to plate"), fit_image, + shortHelp = _("Fit to plate [F]"), + longHelp = '') + self.toolbar.Realize() + self.glpanel = GcodeViewPanel(panel, + build_dimensions = build_dimensions, + realparent = self, + antialias_samples = antialias_samples) + vbox.Add(self.glpanel, 1, flag = wx.EXPAND) + + self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.zoom_to_center(1.2), id = 1) + self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.zoom_to_center(1 / 1.2), id = 2) + self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.layerup(), id = 3) + self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.layerdown(), id = 4) + self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.resetview(), id = 5) + self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.fit(), id = 8) + self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.inject(), id = 6) + self.Bind(wx.EVT_TOOL, lambda x: self.glpanel.editlayer(), id = 7) + + def setlayercb(self, layer): + self.layerslider.SetValue(layer) + self.update_status("") + + def update_status(self, extra): + layer = self.model.num_layers_to_draw + filtered = [k for k, v in self.model.layer_idxs_map.iteritems() if v == layer] + if filtered: + true_layer = filtered[0] + z = self.model.gcode.all_layers[true_layer].z + message = _("Layer %d -%s Z = %.03f mm") % (layer, extra, z) + else: + message = _("Entire object") + wx.CallAfter(self.SetStatusText, message, 0) + + def process_slider(self, event): + new_layer = self.layerslider.GetValue() + new_layer = min(self.model.max_layers + 1, new_layer) + new_layer = max(1, new_layer) + self.model.num_layers_to_draw = new_layer + self.update_status("") + wx.CallAfter(self.Refresh) + + def set_current_gline(self, gline): + if gline.is_move and gline.gcview_end_vertex is not None \ + and self.model and self.model.loaded: + self.model.printed_until = gline.gcview_end_vertex + if not self.refresh_timer.IsRunning(): + self.refresh_timer.Start() + + def recreate_platform(self, build_dimensions, circular): + return recreate_platform(self, build_dimensions, circular) + + def addfile(self, gcode = None): + if self.clonefrom: + self.model = self.clonefrom[-1].model.copy() + self.objects[-1].model = self.model + else: + GcodeViewLoader.addfile(self, gcode) + self.layerslider.SetRange(1, self.model.max_layers + 1) + self.layerslider.SetValue(self.model.max_layers + 1) + wx.CallAfter(self.SetStatusText, _("Entire object"), 0) + wx.CallAfter(self.Refresh) + + def clear(self): + self.model = None + self.objects[-1].model = None + wx.CallAfter(self.Refresh) + +if __name__ == "__main__": + import sys + app = wx.App(redirect = False) + build_dimensions = [200, 200, 100, 0, 0, 0] + title = 'Gcode view, shift to move view, mousewheel to set layer' + frame = GcodeViewFrame(None, wx.ID_ANY, title, size = (400, 400), + build_dimensions = build_dimensions) + gcode = gcoder.GCode(open(sys.argv[1]), get_home_pos(build_dimensions)) + frame.addfile(gcode) + + first_move = None + for i in range(len(gcode.lines)): + if gcode.lines[i].is_move: + first_move = gcode.lines[i] + break + last_move = None + for i in range(len(gcode.lines) - 1, -1, -1): + if gcode.lines[i].is_move: + last_move = gcode.lines[i] + break + nsteps = 20 + steptime = 500 + lines = [first_move] + [gcode.lines[int(float(i) * (len(gcode.lines) - 1) / nsteps)] for i in range(1, nsteps)] + [last_move] + current_line = 0 + + def setLine(): + global current_line + frame.set_current_gline(lines[current_line]) + current_line = (current_line + 1) % len(lines) + timer.Start() + timer = wx.CallLater(steptime, setLine) + timer.Start() + + frame.Show(True) + app.MainLoop() + app.Destroy() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gl/__init__.py diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gl/libtatlin/__init__.py diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gl/libtatlin/actors.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gl/libtatlin/actors.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,1062 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2013 Guillaume Seguin +# Copyright (C) 2011 Denis Kobozev +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import time +import numpy +import array +import math +import logging +import threading + +from ctypes import sizeof + +from pyglet.gl import glPushMatrix, glPopMatrix, glTranslatef, \ + glGenLists, glNewList, GL_COMPILE, glEndList, glCallList, \ + GL_ELEMENT_ARRAY_BUFFER, GL_UNSIGNED_INT, GL_TRIANGLES, GL_LINE_LOOP, \ + GL_ARRAY_BUFFER, GL_STATIC_DRAW, glColor4f, glVertex3f, \ + glBegin, glEnd, GL_LINES, glEnable, glDisable, glGetFloatv, \ + GL_LINE_SMOOTH, glLineWidth, GL_LINE_WIDTH, GLfloat, GL_FLOAT, GLuint, \ + glVertexPointer, glColorPointer, glDrawArrays, glDrawRangeElements, \ + glEnableClientState, glDisableClientState, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, \ + GL_FRONT_AND_BACK, GL_FRONT, glMaterialfv, GL_SPECULAR, GL_EMISSION, \ + glColorMaterial, GL_AMBIENT_AND_DIFFUSE, glMaterialf, GL_SHININESS, \ + GL_NORMAL_ARRAY, glNormalPointer, GL_LIGHTING, glColor3f +from pyglet.graphics.vertexbuffer import create_buffer, VertexBufferObject + +from printrun.utils import install_locale +install_locale('pronterface') + +def vec(*args): + return (GLfloat * len(args))(*args) + +def compile_display_list(func, *options): + display_list = glGenLists(1) + glNewList(display_list, GL_COMPILE) + func(*options) + glEndList() + return display_list + +def numpy2vbo(nparray, target = GL_ARRAY_BUFFER, usage = GL_STATIC_DRAW, use_vbos = True): + vbo = create_buffer(nparray.nbytes, target = target, usage = usage, vbo = use_vbos) + vbo.bind() + vbo.set_data(nparray.ctypes.data) + return vbo + +def triangulate_rectangle(i1, i2, i3, i4): + return [i1, i4, i3, i3, i2, i1] + +def triangulate_box(i1, i2, i3, i4, + j1, j2, j3, j4): + return [i1, i2, j2, j2, j1, i1, i2, i3, j3, j3, j2, i2, + i3, i4, j4, j4, j3, i3, i4, i1, j1, j1, j4, i4] + +class BoundingBox(object): + """ + A rectangular box (cuboid) enclosing a 3D model, defined by lower and upper corners. + """ + def __init__(self, upper_corner, lower_corner): + self.upper_corner = upper_corner + self.lower_corner = lower_corner + + @property + def width(self): + width = abs(self.upper_corner[0] - self.lower_corner[0]) + return round(width, 2) + + @property + def depth(self): + depth = abs(self.upper_corner[1] - self.lower_corner[1]) + return round(depth, 2) + + @property + def height(self): + height = abs(self.upper_corner[2] - self.lower_corner[2]) + return round(height, 2) + + +class Platform(object): + """ + Platform on which models are placed. + """ + graduations_major = 10 + + def __init__(self, build_dimensions, light = False, circular = False): + self.light = light + self.circular = circular + self.width = build_dimensions[0] + self.depth = build_dimensions[1] + self.height = build_dimensions[2] + self.xoffset = build_dimensions[3] + self.yoffset = build_dimensions[4] + self.zoffset = build_dimensions[5] + + self.color_grads_minor = (0xaf / 255, 0xdf / 255, 0x5f / 255, 0.1) + self.color_grads_interm = (0xaf / 255, 0xdf / 255, 0x5f / 255, 0.2) + self.color_grads_major = (0xaf / 255, 0xdf / 255, 0x5f / 255, 0.33) + + self.initialized = False + self.loaded = True + + def init(self): + self.display_list = compile_display_list(self.draw) + self.initialized = True + + def draw(self): + glPushMatrix() + + glTranslatef(self.xoffset, self.yoffset, self.zoffset) + + def color(i): + if i % self.graduations_major == 0: + glColor4f(*self.color_grads_major) + elif i % (self.graduations_major / 2) == 0: + glColor4f(*self.color_grads_interm) + else: + if self.light: return False + glColor4f(*self.color_grads_minor) + return True + + # draw the grid + glBegin(GL_LINES) + if self.circular: # Draw a circular grid + for i in range(0, int(math.ceil(self.width + 1))): + angle = math.asin(2 * float(i) / self.width - 1) + x = (math.cos(angle) + 1) * self.depth / 2 + if color(i): + glVertex3f(float(i), self.depth - x, 0.0) + glVertex3f(float(i), x, 0.0) + + for i in range(0, int(math.ceil(self.depth + 1))): + angle = math.acos(2 * float(i) / self.depth - 1) + x = (math.sin(angle) + 1) * self.width / 2 + if color(i): + glVertex3f(self.width - x, float(i), 0.0) + glVertex3f(x, float(i), 0.0) + else: # Draw a rectangular grid + for i in range(0, int(math.ceil(self.width + 1))): + if color(i): + glVertex3f(float(i), 0.0, 0.0) + glVertex3f(float(i), self.depth, 0.0) + + for i in range(0, int(math.ceil(self.depth + 1))): + if color(i): + glVertex3f(0, float(i), 0.0) + glVertex3f(self.width, float(i), 0.0) + glEnd() + + if self.circular: + glBegin(GL_LINE_LOOP) + for i in range(0, 360): + angle = math.radians(i) + glVertex3f((math.cos(angle) + 1) * self.width / 2, + (math.sin(angle) + 1) * self.depth / 2, 0.0) + glEnd() + + glPopMatrix() + + def display(self, mode_2d=False): + # FIXME: using the list sometimes results in graphical corruptions + # glCallList(self.display_list) + self.draw() + +class PrintHead(object): + def __init__(self): + self.color = (43. / 255, 0., 175. / 255, 1.0) + self.scale = 5 + self.height = 5 + + self.initialized = False + self.loaded = True + + def init(self): + self.display_list = compile_display_list(self.draw) + self.initialized = True + + def draw(self): + glPushMatrix() + + glBegin(GL_LINES) + glColor4f(*self.color) + for di in [-1, 1]: + for dj in [-1, 1]: + glVertex3f(0, 0, 0) + glVertex3f(self.scale * di, self.scale * dj, self.height) + glEnd() + + glPopMatrix() + + def display(self, mode_2d=False): + glEnable(GL_LINE_SMOOTH) + orig_linewidth = (GLfloat)() + glGetFloatv(GL_LINE_WIDTH, orig_linewidth) + glLineWidth(3.0) + glCallList(self.display_list) + glLineWidth(orig_linewidth) + glDisable(GL_LINE_SMOOTH) + +class Model(object): + """ + Parent class for models that provides common functionality. + """ + AXIS_X = (1, 0, 0) + AXIS_Y = (0, 1, 0) + AXIS_Z = (0, 0, 1) + + letter_axis_map = { + 'x': AXIS_X, + 'y': AXIS_Y, + 'z': AXIS_Z, + } + + axis_letter_map = dict([(v, k) for k, v in letter_axis_map.items()]) + + lock = None + + def __init__(self, offset_x=0, offset_y=0): + self.offset_x = offset_x + self.offset_y = offset_y + + self.lock = threading.Lock() + + self.init_model_attributes() + + def init_model_attributes(self): + """ + Set/reset saved properties. + """ + self.invalidate_bounding_box() + self.modified = False + + def invalidate_bounding_box(self): + self._bounding_box = None + + @property + def bounding_box(self): + """ + Get a bounding box for the model. + """ + if self._bounding_box is None: + self._bounding_box = self._calculate_bounding_box() + return self._bounding_box + + def _calculate_bounding_box(self): + """ + Calculate an axis-aligned box enclosing the model. + """ + # swap rows and columns in our vertex arrays so that we can do max and + # min on axis 1 + xyz_rows = self.vertices.reshape(-1, order='F').reshape(3, -1) + lower_corner = xyz_rows.min(1) + upper_corner = xyz_rows.max(1) + box = BoundingBox(upper_corner, lower_corner) + return box + + @property + def width(self): + return self.bounding_box.width + + @property + def depth(self): + return self.bounding_box.depth + + @property + def height(self): + return self.bounding_box.height + + def movement_color(self, move): + """ + Return the color to use for particular type of movement. + """ + if move.extruding: + if move.current_tool == 0: + return self.color_tool0 + elif move.current_tool == 1: + return self.color_tool1 + elif move.current_tool == 2: + return self.color_tool2 + elif move.current_tool == 3: + return self.color_tool3 + else: + return self.color_tool4 + + return self.color_travel + +def movement_angle(src, dst, precision=0): + x = dst[0] - src[0] + y = dst[1] - src[1] + angle = math.degrees(math.atan2(y, -x)) # negate x for clockwise rotation angle + return round(angle, precision) + +def get_next_move(gcode, layer_idx, gline_idx): + gline_idx += 1 + while layer_idx < len(gcode.all_layers): + layer = gcode.all_layers[layer_idx] + while gline_idx < len(layer): + gline = layer[gline_idx] + if gline.is_move: + return gline + gline_idx += 1 + layer_idx += 1 + gline_idx = 0 + return None + +class GcodeModel(Model): + """ + Model for displaying Gcode data. + """ + + color_travel = (0.6, 0.6, 0.6, 0.6) + color_tool0 = (1.0, 0.0, 0.0, 1.0) + color_tool1 = (0.67, 0.05, 0.9, 1.0) + color_tool2 = (1.0, 0.8, 0., 1.0) + color_tool3 = (1.0, 0., 0.62, 1.0) + color_tool4 = (0., 1.0, 0.58, 1.0) + color_printed = (0.2, 0.75, 0, 1.0) + color_current = (0, 0.9, 1.0, 1.0) + color_current_printed = (0.1, 0.4, 0, 1.0) + + display_travels = True + + buffers_created = False + use_vbos = True + loaded = False + fully_loaded = False + + gcode = None + + path_halfwidth = 0.2 + path_halfheight = 0.2 + + def set_path_size(self, path_halfwidth, path_halfheight): + with self.lock: + self.path_halfwidth = path_halfwidth + self.path_halfheight = path_halfheight + + def load_data(self, model_data, callback=None): + t_start = time.time() + self.gcode = model_data + + self.count_travel_indices = count_travel_indices = [0] + self.count_print_indices = count_print_indices = [0] + self.count_print_vertices = count_print_vertices = [0] + + # Some trivial computations, but that's mostly for documentation :) + # Not like 10 multiplications are going to cost much time vs what's + # about to happen :) + + # Max number of values which can be generated per gline + # to store coordinates/colors/normals. + # Nicely enough we have 3 per kind of thing for all kinds. + coordspervertex = 3 + verticesperline = 8 + coordsperline = coordspervertex * verticesperline + coords_count = lambda nlines: nlines * coordsperline + + travelverticesperline = 2 + travelcoordsperline = coordspervertex * travelverticesperline + travel_coords_count = lambda nlines: nlines * travelcoordsperline + + trianglesperface = 2 + facesperbox = 4 + trianglesperbox = trianglesperface * facesperbox + verticespertriangle = 3 + indicesperbox = verticespertriangle * trianglesperbox + boxperline = 2 + indicesperline = indicesperbox * boxperline + indices_count = lambda nlines: nlines * indicesperline + + nlines = len(model_data) + ntravelcoords = travel_coords_count(nlines) + ncoords = coords_count(nlines) + nindices = indices_count(nlines) + travel_vertices = self.travels = numpy.zeros(ntravelcoords, dtype = GLfloat) + travel_vertex_k = 0 + vertices = self.vertices = numpy.zeros(ncoords, dtype = GLfloat) + vertex_k = 0 + colors = self.colors = numpy.zeros(ncoords, dtype = GLfloat) + color_k = 0 + normals = self.normals = numpy.zeros(ncoords, dtype = GLfloat) + normal_k = 0 + indices = self.indices = numpy.zeros(nindices, dtype = GLuint) + index_k = 0 + self.layer_idxs_map = {} + self.layer_stops = [0] + + prev_is_extruding = False + prev_move_normal_x = None + prev_move_normal_y = None + prev_move_angle = None + + prev_pos = (0, 0, 0) + layer_idx = 0 + + self.printed_until = 0 + self.only_current = False + + twopi = 2 * math.pi + + processed_lines = 0 + + while layer_idx < len(model_data.all_layers): + with self.lock: + nlines = len(model_data) + remaining_lines = nlines - processed_lines + # Only reallocate memory which might be needed, not memory + # for everything + ntravelcoords = coords_count(remaining_lines) + travel_vertex_k + ncoords = coords_count(remaining_lines) + vertex_k + nindices = indices_count(remaining_lines) + index_k + if ncoords > vertices.size: + self.travels.resize(ntravelcoords, refcheck = False) + self.vertices.resize(ncoords, refcheck = False) + self.colors.resize(ncoords, refcheck = False) + self.normals.resize(ncoords, refcheck = False) + self.indices.resize(nindices, refcheck = False) + layer = model_data.all_layers[layer_idx] + has_movement = False + for gline_idx, gline in enumerate(layer): + if not gline.is_move: + continue + if gline.x is None and gline.y is None and gline.z is None: + continue + has_movement = True + current_pos = (gline.current_x, gline.current_y, gline.current_z) + if not gline.extruding: + travel_vertices[travel_vertex_k] = prev_pos[0] + travel_vertices[travel_vertex_k + 1] = prev_pos[1] + travel_vertices[travel_vertex_k + 2] = prev_pos[2] + travel_vertices[travel_vertex_k + 3] = current_pos[0] + travel_vertices[travel_vertex_k + 4] = current_pos[1] + travel_vertices[travel_vertex_k + 5] = current_pos[2] + travel_vertex_k += 6 + prev_is_extruding = False + else: + gline_color = self.movement_color(gline) + + next_move = get_next_move(model_data, layer_idx, gline_idx) + next_is_extruding = (next_move.extruding + if next_move is not None else False) + + delta_x = current_pos[0] - prev_pos[0] + delta_y = current_pos[1] - prev_pos[1] + norm = delta_x * delta_x + delta_y * delta_y + if norm == 0: # Don't draw anything if this move is Z+E only + continue + norm = math.sqrt(norm) + move_normal_x = - delta_y / norm + move_normal_y = delta_x / norm + move_angle = math.atan2(delta_y, delta_x) + + # FIXME: compute these dynamically + path_halfwidth = self.path_halfwidth * 1.2 + path_halfheight = self.path_halfheight * 1.2 + + new_indices = [] + new_vertices = [] + new_normals = [] + if prev_is_extruding: + # Store previous vertices indices + prev_id = vertex_k / 3 - 4 + avg_move_normal_x = (prev_move_normal_x + move_normal_x) / 2 + avg_move_normal_y = (prev_move_normal_y + move_normal_y) / 2 + norm = avg_move_normal_x * avg_move_normal_x + avg_move_normal_y * avg_move_normal_y + if norm == 0: + avg_move_normal_x = move_normal_x + avg_move_normal_y = move_normal_y + else: + norm = math.sqrt(norm) + avg_move_normal_x /= norm + avg_move_normal_y /= norm + delta_angle = move_angle - prev_move_angle + delta_angle = (delta_angle + twopi) % twopi + fact = abs(math.cos(delta_angle / 2)) + # If move is turning too much, avoid creating a big peak + # by adding an intermediate box + if fact < 0.5: + # FIXME: It looks like there's some heavy code duplication here... + hw = path_halfwidth + p1x = prev_pos[0] - hw * prev_move_normal_x + p2x = prev_pos[0] + hw * prev_move_normal_x + p1y = prev_pos[1] - hw * prev_move_normal_y + p2y = prev_pos[1] + hw * prev_move_normal_y + new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) + new_vertices.extend((p1x, p1y, prev_pos[2])) + new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) + new_vertices.extend((p2x, p2y, prev_pos[2])) + new_normals.extend((0, 0, 1)) + new_normals.extend((-prev_move_normal_x, -prev_move_normal_y, 0)) + new_normals.extend((0, 0, -1)) + new_normals.extend((prev_move_normal_x, prev_move_normal_y, 0)) + first = vertex_k / 3 + # Link to previous + new_indices += triangulate_box(prev_id, prev_id + 1, + prev_id + 2, prev_id + 3, + first, first + 1, + first + 2, first + 3) + p1x = prev_pos[0] - hw * move_normal_x + p2x = prev_pos[0] + hw * move_normal_x + p1y = prev_pos[1] - hw * move_normal_y + p2y = prev_pos[1] + hw * move_normal_y + new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) + new_vertices.extend((p1x, p1y, prev_pos[2])) + new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) + new_vertices.extend((p2x, p2y, prev_pos[2])) + new_normals.extend((0, 0, 1)) + new_normals.extend((-move_normal_x, -move_normal_y, 0)) + new_normals.extend((0, 0, -1)) + new_normals.extend((move_normal_x, move_normal_y, 0)) + prev_id += 4 + first += 4 + # Link to previous + new_indices += triangulate_box(prev_id, prev_id + 1, + prev_id + 2, prev_id + 3, + first, first + 1, + first + 2, first + 3) + else: + hw = path_halfwidth / fact + # Compute vertices + p1x = prev_pos[0] - hw * avg_move_normal_x + p2x = prev_pos[0] + hw * avg_move_normal_x + p1y = prev_pos[1] - hw * avg_move_normal_y + p2y = prev_pos[1] + hw * avg_move_normal_y + new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) + new_vertices.extend((p1x, p1y, prev_pos[2])) + new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) + new_vertices.extend((p2x, p2y, prev_pos[2])) + new_normals.extend((0, 0, 1)) + new_normals.extend((-avg_move_normal_x, -avg_move_normal_y, 0)) + new_normals.extend((0, 0, -1)) + new_normals.extend((avg_move_normal_x, avg_move_normal_y, 0)) + first = vertex_k / 3 + # Link to previous + new_indices += triangulate_box(prev_id, prev_id + 1, + prev_id + 2, prev_id + 3, + first, first + 1, + first + 2, first + 3) + else: + # Compute vertices normal to the current move and cap it + p1x = prev_pos[0] - path_halfwidth * move_normal_x + p2x = prev_pos[0] + path_halfwidth * move_normal_x + p1y = prev_pos[1] - path_halfwidth * move_normal_y + p2y = prev_pos[1] + path_halfwidth * move_normal_y + new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] + path_halfheight)) + new_vertices.extend((p1x, p1y, prev_pos[2])) + new_vertices.extend((prev_pos[0], prev_pos[1], prev_pos[2] - path_halfheight)) + new_vertices.extend((p2x, p2y, prev_pos[2])) + new_normals.extend((0, 0, 1)) + new_normals.extend((-move_normal_x, -move_normal_y, 0)) + new_normals.extend((0, 0, -1)) + new_normals.extend((move_normal_x, move_normal_y, 0)) + first = vertex_k / 3 + new_indices = triangulate_rectangle(first, first + 1, + first + 2, first + 3) + + if not next_is_extruding: + # Compute caps and link everything + p1x = current_pos[0] - path_halfwidth * move_normal_x + p2x = current_pos[0] + path_halfwidth * move_normal_x + p1y = current_pos[1] - path_halfwidth * move_normal_y + p2y = current_pos[1] + path_halfwidth * move_normal_y + new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] + path_halfheight)) + new_vertices.extend((p1x, p1y, current_pos[2])) + new_vertices.extend((current_pos[0], current_pos[1], current_pos[2] - path_halfheight)) + new_vertices.extend((p2x, p2y, current_pos[2])) + new_normals.extend((0, 0, 1)) + new_normals.extend((-move_normal_x, -move_normal_y, 0)) + new_normals.extend((0, 0, -1)) + new_normals.extend((move_normal_x, move_normal_y, 0)) + end_first = vertex_k / 3 + len(new_vertices) / 3 - 4 + new_indices += triangulate_rectangle(end_first + 3, end_first + 2, + end_first + 1, end_first) + new_indices += triangulate_box(first, first + 1, + first + 2, first + 3, + end_first, end_first + 1, + end_first + 2, end_first + 3) + + for new_i, item in enumerate(new_indices): + indices[index_k + new_i] = item + index_k += len(new_indices) + for new_i, item in enumerate(new_vertices): + vertices[vertex_k + new_i] = item + vertex_k += len(new_vertices) + for new_i, item in enumerate(new_normals): + normals[normal_k + new_i] = item + normal_k += len(new_normals) + new_colors = list(gline_color)[:-1] * (len(new_vertices) / 3) + for new_i, item in enumerate(new_colors): + colors[color_k + new_i] = item + color_k += len(new_colors) + + prev_is_extruding = True + prev_move_normal_x = move_normal_x + prev_move_normal_y = move_normal_y + prev_move_angle = move_angle + + prev_pos = current_pos + count_travel_indices.append(travel_vertex_k / 3) + count_print_indices.append(index_k) + count_print_vertices.append(vertex_k / 3) + gline.gcview_end_vertex = len(count_print_indices) - 1 + + if has_movement: + self.layer_stops.append(len(count_print_indices) - 1) + self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1 + self.max_layers = len(self.layer_stops) - 1 + self.num_layers_to_draw = self.max_layers + 1 + self.initialized = False + self.loaded = True + + processed_lines += len(layer) + + if callback: + callback(layer_idx + 1) + + yield layer_idx + layer_idx += 1 + + with self.lock: + self.dims = ((model_data.xmin, model_data.xmax, model_data.width), + (model_data.ymin, model_data.ymax, model_data.depth), + (model_data.zmin, model_data.zmax, model_data.height)) + + self.travels.resize(travel_vertex_k, refcheck = False) + self.vertices.resize(vertex_k, refcheck = False) + self.colors.resize(color_k, refcheck = False) + self.normals.resize(normal_k, refcheck = False) + self.indices.resize(index_k, refcheck = False) + + self.layer_stops = array.array('L', self.layer_stops) + self.count_travel_indices = array.array('L', count_travel_indices) + self.count_print_indices = array.array('L', count_print_indices) + self.count_print_vertices = array.array('L', count_print_vertices) + + self.max_layers = len(self.layer_stops) - 1 + self.num_layers_to_draw = self.max_layers + 1 + self.loaded = True + self.initialized = False + self.loaded = True + self.fully_loaded = True + + t_end = time.time() + + logging.debug(_('Initialized 3D visualization in %.2f seconds') % (t_end - t_start)) + logging.debug(_('Vertex count: %d') % ((len(self.vertices) + len(self.travels)) / 3)) + yield None + + def copy(self): + copy = GcodeModel() + for var in ["vertices", "colors", "travels", "indices", "normals", + "max_layers", "num_layers_to_draw", "printed_until", + "layer_stops", "dims", "only_current", + "layer_idxs_map", "count_travel_indices", + "count_print_indices", "count_print_vertices", + "path_halfwidth", "path_halfheight", + "gcode"]: + setattr(copy, var, getattr(self, var)) + copy.loaded = True + copy.fully_loaded = True + copy.initialized = False + return copy + + # ------------------------------------------------------------------------ + # DRAWING + # ------------------------------------------------------------------------ + + def init(self): + with self.lock: + self.layers_loaded = self.max_layers + self.initialized = True + if self.buffers_created: + self.travel_buffer.delete() + self.index_buffer.delete() + self.vertex_buffer.delete() + self.vertex_color_buffer.delete() + self.vertex_normal_buffer.delete() + self.travel_buffer = numpy2vbo(self.travels, use_vbos = self.use_vbos) + self.index_buffer = numpy2vbo(self.indices, use_vbos = self.use_vbos, + target = GL_ELEMENT_ARRAY_BUFFER) + self.vertex_buffer = numpy2vbo(self.vertices, use_vbos = self.use_vbos) + self.vertex_color_buffer = numpy2vbo(self.colors, use_vbos = self.use_vbos) + self.vertex_normal_buffer = numpy2vbo(self.normals, use_vbos = self.use_vbos) + if self.fully_loaded: + # Delete numpy arrays after creating VBOs after full load + self.travels = None + self.indices = None + self.vertices = None + self.colors = None + self.normals = None + self.buffers_created = True + + def display(self, mode_2d=False): + with self.lock: + glPushMatrix() + glTranslatef(self.offset_x, self.offset_y, 0) + glEnableClientState(GL_VERTEX_ARRAY) + + has_vbo = isinstance(self.vertex_buffer, VertexBufferObject) + if self.display_travels: + self._display_travels(has_vbo) + + glEnable(GL_LIGHTING) + glEnableClientState(GL_NORMAL_ARRAY) + glEnableClientState(GL_COLOR_ARRAY) + glMaterialfv(GL_FRONT, GL_SPECULAR, vec(1, 1, 1, 1)) + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, vec(0, 0, 0, 0)) + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50) + + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) + self._display_movements(has_vbo) + + glDisable(GL_LIGHTING) + + glDisableClientState(GL_COLOR_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + glDisableClientState(GL_NORMAL_ARRAY) + + glPopMatrix() + + def _display_travels(self, has_vbo): + self.travel_buffer.bind() + glVertexPointer(3, GL_FLOAT, 0, self.travel_buffer.ptr) + + # Prevent race condition by using the number of currently loaded layers + max_layers = self.layers_loaded + # TODO: show current layer travels in a different color + end = self.layer_stops[min(self.num_layers_to_draw, max_layers)] + end_index = self.count_travel_indices[end] + glColor4f(*self.color_travel) + if self.only_current: + if self.num_layers_to_draw < max_layers: + end_prev_layer = self.layer_stops[self.num_layers_to_draw - 1] + start_index = self.count_travel_indices[end_prev_layer + 1] + glDrawArrays(GL_LINES, start_index, end_index - start_index + 1) + else: + glDrawArrays(GL_LINES, 0, end_index) + + self.travel_buffer.unbind() + + def _draw_elements(self, start, end, draw_type = GL_TRIANGLES): + # Don't attempt printing empty layer + if self.count_print_indices[end] == self.count_print_indices[start - 1]: + return + glDrawRangeElements(draw_type, + self.count_print_vertices[start - 1], + self.count_print_vertices[end] - 1, + self.count_print_indices[end] - self.count_print_indices[start - 1], + GL_UNSIGNED_INT, + sizeof(GLuint) * self.count_print_indices[start - 1]) + + def _display_movements(self, has_vbo): + self.vertex_buffer.bind() + glVertexPointer(3, GL_FLOAT, 0, self.vertex_buffer.ptr) + + self.vertex_color_buffer.bind() + glColorPointer(3, GL_FLOAT, 0, self.vertex_color_buffer.ptr) + + self.vertex_normal_buffer.bind() + glNormalPointer(GL_FLOAT, 0, self.vertex_normal_buffer.ptr) + + self.index_buffer.bind() + + # Prevent race condition by using the number of currently loaded layers + max_layers = self.layers_loaded + + start = 1 + layer_selected = self.num_layers_to_draw <= max_layers + if layer_selected: + end_prev_layer = self.layer_stops[self.num_layers_to_draw - 1] + else: + end_prev_layer = 0 + end = self.layer_stops[min(self.num_layers_to_draw, max_layers)] + + glDisableClientState(GL_COLOR_ARRAY) + + glColor3f(*self.color_printed[:-1]) + + # Draw printed stuff until end or end_prev_layer + cur_end = min(self.printed_until, end) + if not self.only_current: + if 1 <= end_prev_layer <= cur_end: + self._draw_elements(1, end_prev_layer) + elif cur_end >= 1: + self._draw_elements(1, cur_end) + + glEnableClientState(GL_COLOR_ARRAY) + + # Draw nonprinted stuff until end_prev_layer + start = max(cur_end, 1) + if end_prev_layer >= start: + if not self.only_current: + self._draw_elements(start, end_prev_layer) + cur_end = end_prev_layer + + # Draw current layer + if layer_selected: + glDisableClientState(GL_COLOR_ARRAY) + + glColor3f(*self.color_current_printed[:-1]) + + if cur_end > end_prev_layer: + self._draw_elements(end_prev_layer + 1, cur_end) + + glColor3f(*self.color_current[:-1]) + + if end > cur_end: + self._draw_elements(cur_end + 1, end) + + glEnableClientState(GL_COLOR_ARRAY) + + # Draw non printed stuff until end (if not ending at a given layer) + start = max(self.printed_until, 1) + if not layer_selected and end >= start: + self._draw_elements(start, end) + + self.index_buffer.unbind() + self.vertex_buffer.unbind() + self.vertex_color_buffer.unbind() + self.vertex_normal_buffer.unbind() + +class GcodeModelLight(Model): + """ + Model for displaying Gcode data. + """ + + color_travel = (0.6, 0.6, 0.6, 0.6) + color_tool0 = (1.0, 0.0, 0.0, 0.6) + color_tool1 = (0.67, 0.05, 0.9, 0.6) + color_tool2 = (1.0, 0.8, 0., 0.6) + color_tool3 = (1.0, 0., 0.62, 0.6) + color_tool4 = (0., 1.0, 0.58, 0.6) + color_printed = (0.2, 0.75, 0, 0.6) + color_current = (0, 0.9, 1.0, 0.8) + color_current_printed = (0.1, 0.4, 0, 0.8) + + buffers_created = False + use_vbos = True + loaded = False + fully_loaded = False + + gcode = None + + def load_data(self, model_data, callback=None): + t_start = time.time() + self.gcode = model_data + + self.layer_idxs_map = {} + self.layer_stops = [0] + + prev_pos = (0, 0, 0) + layer_idx = 0 + nlines = len(model_data) + vertices = self.vertices = numpy.zeros(nlines * 6, dtype = GLfloat) + vertex_k = 0 + colors = self.colors = numpy.zeros(nlines * 8, dtype = GLfloat) + color_k = 0 + self.printed_until = -1 + self.only_current = False + while layer_idx < len(model_data.all_layers): + with self.lock: + nlines = len(model_data) + if nlines * 6 != vertices.size: + self.vertices.resize(nlines * 6, refcheck = False) + self.colors.resize(nlines * 8, refcheck = False) + layer = model_data.all_layers[layer_idx] + has_movement = False + for gline in layer: + if not gline.is_move: + continue + if gline.x is None and gline.y is None and gline.z is None: + continue + has_movement = True + vertices[vertex_k] = prev_pos[0] + vertices[vertex_k + 1] = prev_pos[1] + vertices[vertex_k + 2] = prev_pos[2] + current_pos = (gline.current_x, gline.current_y, gline.current_z) + vertices[vertex_k + 3] = current_pos[0] + vertices[vertex_k + 4] = current_pos[1] + vertices[vertex_k + 5] = current_pos[2] + vertex_k += 6 + + vertex_color = self.movement_color(gline) + colors[color_k] = vertex_color[0] + colors[color_k + 1] = vertex_color[1] + colors[color_k + 2] = vertex_color[2] + colors[color_k + 3] = vertex_color[3] + colors[color_k + 4] = vertex_color[0] + colors[color_k + 5] = vertex_color[1] + colors[color_k + 6] = vertex_color[2] + colors[color_k + 7] = vertex_color[3] + color_k += 8 + + prev_pos = current_pos + gline.gcview_end_vertex = vertex_k / 3 + + if has_movement: + self.layer_stops.append(vertex_k / 3) + self.layer_idxs_map[layer_idx] = len(self.layer_stops) - 1 + self.max_layers = len(self.layer_stops) - 1 + self.num_layers_to_draw = self.max_layers + 1 + self.initialized = False + self.loaded = True + + if callback: + callback(layer_idx + 1) + + yield layer_idx + layer_idx += 1 + + with self.lock: + self.dims = ((model_data.xmin, model_data.xmax, model_data.width), + (model_data.ymin, model_data.ymax, model_data.depth), + (model_data.zmin, model_data.zmax, model_data.height)) + + self.vertices.resize(vertex_k, refcheck = False) + self.colors.resize(color_k, refcheck = False) + self.max_layers = len(self.layer_stops) - 1 + self.num_layers_to_draw = self.max_layers + 1 + self.initialized = False + self.loaded = True + self.fully_loaded = True + + t_end = time.time() + + logging.debug(_('Initialized 3D visualization in %.2f seconds') % (t_end - t_start)) + logging.debug(_('Vertex count: %d') % (len(self.vertices) / 3)) + yield None + + def copy(self): + copy = GcodeModelLight() + for var in ["vertices", "colors", "max_layers", + "num_layers_to_draw", "printed_until", + "layer_stops", "dims", "only_current", + "layer_idxs_map", "gcode"]: + setattr(copy, var, getattr(self, var)) + copy.loaded = True + copy.fully_loaded = True + copy.initialized = False + return copy + + # ------------------------------------------------------------------------ + # DRAWING + # ------------------------------------------------------------------------ + + def init(self): + with self.lock: + self.layers_loaded = self.max_layers + self.initialized = True + if self.buffers_created: + self.vertex_buffer.delete() + self.vertex_color_buffer.delete() + self.vertex_buffer = numpy2vbo(self.vertices, use_vbos = self.use_vbos) + self.vertex_color_buffer = numpy2vbo(self.colors, use_vbos = self.use_vbos) # each pair of vertices shares the color + if self.fully_loaded: + # Delete numpy arrays after creating VBOs after full load + self.vertices = None + self.colors = None + self.buffers_created = True + + def display(self, mode_2d=False): + with self.lock: + glPushMatrix() + glTranslatef(self.offset_x, self.offset_y, 0) + glEnableClientState(GL_VERTEX_ARRAY) + glEnableClientState(GL_COLOR_ARRAY) + + self._display_movements(mode_2d) + + glDisableClientState(GL_COLOR_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + glPopMatrix() + + def _display_movements(self, mode_2d=False): + self.vertex_buffer.bind() + has_vbo = isinstance(self.vertex_buffer, VertexBufferObject) + if has_vbo: + glVertexPointer(3, GL_FLOAT, 0, None) + else: + glVertexPointer(3, GL_FLOAT, 0, self.vertex_buffer.ptr) + + self.vertex_color_buffer.bind() + if has_vbo: + glColorPointer(4, GL_FLOAT, 0, None) + else: + glColorPointer(4, GL_FLOAT, 0, self.vertex_color_buffer.ptr) + + # Prevent race condition by using the number of currently loaded layers + max_layers = self.layers_loaded + + start = 0 + if self.num_layers_to_draw <= max_layers: + end_prev_layer = self.layer_stops[self.num_layers_to_draw - 1] + else: + end_prev_layer = -1 + end = self.layer_stops[min(self.num_layers_to_draw, max_layers)] + + glDisableClientState(GL_COLOR_ARRAY) + + glColor4f(*self.color_printed) + + # Draw printed stuff until end or end_prev_layer + cur_end = min(self.printed_until, end) + if not self.only_current: + if 0 <= end_prev_layer <= cur_end: + glDrawArrays(GL_LINES, start, end_prev_layer) + elif cur_end >= 0: + glDrawArrays(GL_LINES, start, cur_end) + + glEnableClientState(GL_COLOR_ARRAY) + + # Draw nonprinted stuff until end_prev_layer + start = max(cur_end, 0) + if end_prev_layer >= start: + if not self.only_current: + glDrawArrays(GL_LINES, start, end_prev_layer - start) + cur_end = end_prev_layer + + # Draw current layer + if end_prev_layer >= 0: + glDisableClientState(GL_COLOR_ARRAY) + + # Backup & increase line width + orig_linewidth = (GLfloat)() + glGetFloatv(GL_LINE_WIDTH, orig_linewidth) + glLineWidth(2.0) + + glColor4f(*self.color_current_printed) + + if cur_end > end_prev_layer: + glDrawArrays(GL_LINES, end_prev_layer, cur_end - end_prev_layer) + + glColor4f(*self.color_current) + + if end > cur_end: + glDrawArrays(GL_LINES, cur_end, end - cur_end) + + # Restore line width + glLineWidth(orig_linewidth) + + glEnableClientState(GL_COLOR_ARRAY) + + # Draw non printed stuff until end (if not ending at a given layer) + start = max(self.printed_until, 0) + end = end - start + if end_prev_layer < 0 and end > 0 and not self.only_current: + glDrawArrays(GL_LINES, start, end) + + self.vertex_buffer.unbind() + self.vertex_color_buffer.unbind() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gl/panel.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gl/panel.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,355 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +from threading import Lock +import logging +import traceback +import numpy +import numpy.linalg + +import wx +from wx import glcanvas + +import pyglet +pyglet.options['debug_gl'] = True + +from pyglet.gl import glEnable, glDisable, GL_LIGHTING, glLightfv, \ + GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, GL_POSITION, GL_DIFFUSE, \ + GL_AMBIENT, GL_SPECULAR, GL_COLOR_MATERIAL, \ + glShadeModel, GL_SMOOTH, GL_NORMALIZE, \ + GL_BLEND, glBlendFunc, glClear, glClearColor, \ + glClearDepth, GL_COLOR_BUFFER_BIT, GL_CULL_FACE, \ + GL_DEPTH_BUFFER_BIT, glDepthFunc, GL_DEPTH_TEST, \ + GLdouble, glGetDoublev, glGetIntegerv, GLint, \ + GL_LEQUAL, glLoadIdentity, glMatrixMode, GL_MODELVIEW, \ + GL_MODELVIEW_MATRIX, GL_ONE_MINUS_SRC_ALPHA, glOrtho, \ + GL_PROJECTION, GL_PROJECTION_MATRIX, glScalef, \ + GL_SRC_ALPHA, glTranslatef, gluPerspective, gluUnProject, \ + glViewport, GL_VIEWPORT +from pyglet import gl +from .trackball import trackball, mulquat +from .libtatlin.actors import vec + +class wxGLPanel(wx.Panel): + '''A simple class for using OpenGL with wxPython.''' + + orthographic = True + color_background = (0.98, 0.98, 0.78, 1) + do_lights = True + + def __init__(self, parent, id, pos = wx.DefaultPosition, + size = wx.DefaultSize, style = 0, + antialias_samples = 0): + # Forcing a no full repaint to stop flickering + style = style | wx.NO_FULL_REPAINT_ON_RESIZE + super(wxGLPanel, self).__init__(parent, id, pos, size, style) + + self.GLinitialized = False + self.mview_initialized = False + attribList = (glcanvas.WX_GL_RGBA, # RGBA + glcanvas.WX_GL_DOUBLEBUFFER, # Double Buffered + glcanvas.WX_GL_DEPTH_SIZE, 24) # 24 bit + + if antialias_samples > 0 and hasattr(glcanvas, "WX_GL_SAMPLE_BUFFERS"): + attribList += (glcanvas.WX_GL_SAMPLE_BUFFERS, 1, + glcanvas.WX_GL_SAMPLES, antialias_samples) + + self.width = None + self.height = None + + self.sizer = wx.BoxSizer(wx.HORIZONTAL) + self.canvas = glcanvas.GLCanvas(self, attribList = attribList) + self.context = glcanvas.GLContext(self.canvas) + self.sizer.Add(self.canvas, 1, wx.EXPAND) + self.SetSizerAndFit(self.sizer) + + self.rot_lock = Lock() + self.basequat = [0, 0, 0, 1] + self.zoom_factor = 1.0 + + self.gl_broken = False + + # bind events + self.canvas.Bind(wx.EVT_ERASE_BACKGROUND, self.processEraseBackgroundEvent) + self.canvas.Bind(wx.EVT_SIZE, self.processSizeEvent) + self.canvas.Bind(wx.EVT_PAINT, self.processPaintEvent) + + def processEraseBackgroundEvent(self, event): + '''Process the erase background event.''' + pass # Do nothing, to avoid flashing on MSWin + + def processSizeEvent(self, event): + '''Process the resize event.''' + if self.IsFrozen(): + event.Skip() + return + if (wx.VERSION > (2, 9) and self.canvas.IsShownOnScreen()) or self.canvas.GetContext(): + # Make sure the frame is shown before calling SetCurrent. + self.canvas.SetCurrent(self.context) + self.OnReshape() + self.Refresh(False) + timer = wx.CallLater(100, self.Refresh) + timer.Start() + event.Skip() + + def processPaintEvent(self, event): + '''Process the drawing event.''' + self.canvas.SetCurrent(self.context) + + if not self.gl_broken: + try: + self.OnInitGL() + self.OnDraw() + except pyglet.gl.lib.GLException: + self.gl_broken = True + logging.error(_("OpenGL failed, disabling it:") + + "\n" + traceback.format_exc()) + event.Skip() + + def Destroy(self): + # clean up the pyglet OpenGL context + self.pygletcontext.destroy() + # call the super method + super(wxGLPanel, self).Destroy() + + # ========================================================================== + # GLFrame OpenGL Event Handlers + # ========================================================================== + def OnInitGL(self, call_reshape = True): + '''Initialize OpenGL for use in the window.''' + if self.GLinitialized: + return + self.GLinitialized = True + # create a pyglet context for this panel + self.pygletcontext = gl.Context(gl.current_context) + self.pygletcontext.canvas = self + self.pygletcontext.set_current() + # normal gl init + glClearColor(*self.color_background) + glClearDepth(1.0) # set depth value to 1 + glDepthFunc(GL_LEQUAL) + glEnable(GL_COLOR_MATERIAL) + glEnable(GL_DEPTH_TEST) + glEnable(GL_CULL_FACE) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + if call_reshape: + self.OnReshape() + + def OnReshape(self): + """Reshape the OpenGL viewport based on the size of the window""" + size = self.GetClientSize() + oldwidth, oldheight = self.width, self.height + width, height = size.width, size.height + if width < 1 or height < 1: + return + self.width = max(float(width), 1.0) + self.height = max(float(height), 1.0) + self.OnInitGL(call_reshape = False) + glViewport(0, 0, width, height) + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + if self.orthographic: + glOrtho(-width / 2, width / 2, -height / 2, height / 2, + -5 * self.dist, 5 * self.dist) + else: + gluPerspective(60., float(width) / height, 10.0, 3 * self.dist) + glTranslatef(0, 0, -self.dist) # Move back + glMatrixMode(GL_MODELVIEW) + + if not self.mview_initialized: + self.reset_mview(0.9) + self.mview_initialized = True + elif oldwidth is not None and oldheight is not None: + wratio = self.width / oldwidth + hratio = self.height / oldheight + + factor = min(wratio * self.zoomed_width, hratio * self.zoomed_height) + x, y, _ = self.mouse_to_3d(self.width / 2, self.height / 2) + self.zoom(factor, (x, y)) + self.zoomed_width *= wratio / factor + self.zoomed_height *= hratio / factor + + # Wrap text to the width of the window + if self.GLinitialized: + self.pygletcontext.set_current() + self.update_object_resize() + + def setup_lights(self): + if not self.do_lights: + return + glEnable(GL_LIGHTING) + glDisable(GL_LIGHT0) + glLightfv(GL_LIGHT0, GL_AMBIENT, vec(0.4, 0.4, 0.4, 1.0)) + glLightfv(GL_LIGHT0, GL_SPECULAR, vec(0, 0, 0, 0)) + glLightfv(GL_LIGHT0, GL_DIFFUSE, vec(0, 0, 0, 0)) + glEnable(GL_LIGHT1) + glLightfv(GL_LIGHT1, GL_AMBIENT, vec(0, 0, 0, 1.0)) + glLightfv(GL_LIGHT1, GL_SPECULAR, vec(0.6, 0.6, 0.6, 1.0)) + glLightfv(GL_LIGHT2, GL_DIFFUSE, vec(0.8, 0.8, 0.8, 1)) + glLightfv(GL_LIGHT1, GL_POSITION, vec(1, 2, 3, 0)) + glEnable(GL_LIGHT2) + glLightfv(GL_LIGHT2, GL_AMBIENT, vec(0, 0, 0, 1.0)) + glLightfv(GL_LIGHT2, GL_SPECULAR, vec(0.6, 0.6, 0.6, 1.0)) + glLightfv(GL_LIGHT2, GL_DIFFUSE, vec(0.8, 0.8, 0.8, 1)) + glLightfv(GL_LIGHT2, GL_POSITION, vec(-1, -1, 3, 0)) + glEnable(GL_NORMALIZE) + glShadeModel(GL_SMOOTH) + + def reset_mview(self, factor): + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + self.setup_lights() + if self.orthographic: + wratio = self.width / self.dist + hratio = self.height / self.dist + minratio = float(min(wratio, hratio)) + self.zoom_factor = 1.0 + self.zoomed_width = wratio / minratio + self.zoomed_height = hratio / minratio + glScalef(factor * minratio, factor * minratio, 1) + + def OnDraw(self, *args, **kwargs): + """Draw the window.""" + self.pygletcontext.set_current() + glClearColor(*self.color_background) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + self.draw_objects() + self.canvas.SwapBuffers() + + # ========================================================================== + # To be implemented by a sub class + # ========================================================================== + def create_objects(self): + '''create opengl objects when opengl is initialized''' + pass + + def update_object_resize(self): + '''called when the window recieves only if opengl is initialized''' + pass + + def draw_objects(self): + '''called in the middle of ondraw after the buffer has been cleared''' + pass + + # ========================================================================== + # Utils + # ========================================================================== + def get_modelview_mat(self, local_transform): + mvmat = (GLdouble * 16)() + glGetDoublev(GL_MODELVIEW_MATRIX, mvmat) + return mvmat + + def mouse_to_3d(self, x, y, z = 1.0, local_transform = False): + x = float(x) + y = self.height - float(y) + # The following could work if we were not initially scaling to zoom on + # the bed + # if self.orthographic: + # return (x - self.width / 2, y - self.height / 2, 0) + pmat = (GLdouble * 16)() + mvmat = self.get_modelview_mat(local_transform) + viewport = (GLint * 4)() + px = (GLdouble)() + py = (GLdouble)() + pz = (GLdouble)() + glGetIntegerv(GL_VIEWPORT, viewport) + glGetDoublev(GL_PROJECTION_MATRIX, pmat) + glGetDoublev(GL_MODELVIEW_MATRIX, mvmat) + gluUnProject(x, y, z, mvmat, pmat, viewport, px, py, pz) + return (px.value, py.value, pz.value) + + def mouse_to_ray(self, x, y, local_transform = False): + x = float(x) + y = self.height - float(y) + pmat = (GLdouble * 16)() + mvmat = (GLdouble * 16)() + viewport = (GLint * 4)() + px = (GLdouble)() + py = (GLdouble)() + pz = (GLdouble)() + glGetIntegerv(GL_VIEWPORT, viewport) + glGetDoublev(GL_PROJECTION_MATRIX, pmat) + mvmat = self.get_modelview_mat(local_transform) + gluUnProject(x, y, 1, mvmat, pmat, viewport, px, py, pz) + ray_far = (px.value, py.value, pz.value) + gluUnProject(x, y, 0., mvmat, pmat, viewport, px, py, pz) + ray_near = (px.value, py.value, pz.value) + return ray_near, ray_far + + def mouse_to_plane(self, x, y, plane_normal, plane_offset, local_transform = False): + # Ray/plane intersection + ray_near, ray_far = self.mouse_to_ray(x, y, local_transform) + ray_near = numpy.array(ray_near) + ray_far = numpy.array(ray_far) + ray_dir = ray_far - ray_near + ray_dir = ray_dir / numpy.linalg.norm(ray_dir) + plane_normal = numpy.array(plane_normal) + q = ray_dir.dot(plane_normal) + if q == 0: + return None + t = - (ray_near.dot(plane_normal) + plane_offset) / q + if t < 0: + return None + return ray_near + t * ray_dir + + def zoom(self, factor, to = None): + glMatrixMode(GL_MODELVIEW) + if to: + delta_x = to[0] + delta_y = to[1] + glTranslatef(delta_x, delta_y, 0) + glScalef(factor, factor, 1) + self.zoom_factor *= factor + if to: + glTranslatef(-delta_x, -delta_y, 0) + wx.CallAfter(self.Refresh) + + def zoom_to_center(self, factor): + self.canvas.SetCurrent(self.context) + x, y, _ = self.mouse_to_3d(self.width / 2, self.height / 2) + self.zoom(factor, (x, y)) + + def handle_rotation(self, event): + if self.initpos is None: + self.initpos = event.GetPositionTuple() + else: + p1 = self.initpos + p2 = event.GetPositionTuple() + sz = self.GetClientSize() + p1x = float(p1[0]) / (sz[0] / 2) - 1 + p1y = 1 - float(p1[1]) / (sz[1] / 2) + p2x = float(p2[0]) / (sz[0] / 2) - 1 + p2y = 1 - float(p2[1]) / (sz[1] / 2) + quat = trackball(p1x, p1y, p2x, p2y, self.dist / 250.0) + with self.rot_lock: + self.basequat = mulquat(self.basequat, quat) + self.initpos = p2 + + def handle_translation(self, event): + if self.initpos is None: + self.initpos = event.GetPositionTuple() + else: + p1 = self.initpos + p2 = event.GetPositionTuple() + if self.orthographic: + x1, y1, _ = self.mouse_to_3d(p1[0], p1[1]) + x2, y2, _ = self.mouse_to_3d(p2[0], p2[1]) + glTranslatef(x2 - x1, y2 - y1, 0) + else: + glTranslatef(p2[0] - p1[0], -(p2[1] - p1[1]), 0) + self.initpos = p2 diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gl/trackball.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gl/trackball.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import math + +from pyglet.gl import GLdouble + +def cross(v1, v2): + return [v1[1] * v2[2] - v1[2] * v2[1], + v1[2] * v2[0] - v1[0] * v2[2], + v1[0] * v2[1] - v1[1] * v2[0]] + +def trackball(p1x, p1y, p2x, p2y, r): + TRACKBALLSIZE = r + + if p1x == p2x and p1y == p2y: + return [0.0, 0.0, 0.0, 1.0] + + p1 = [p1x, p1y, project_to_sphere(TRACKBALLSIZE, p1x, p1y)] + p2 = [p2x, p2y, project_to_sphere(TRACKBALLSIZE, p2x, p2y)] + a = cross(p2, p1) + + d = map(lambda x, y: x - y, p1, p2) + t = math.sqrt(sum(map(lambda x: x * x, d))) / (2.0 * TRACKBALLSIZE) + + if t > 1.0: + t = 1.0 + if t < -1.0: + t = -1.0 + phi = 2.0 * math.asin(t) + + return axis_to_quat(a, phi) + +def axis_to_quat(a, phi): + lena = math.sqrt(sum(map(lambda x: x * x, a))) + q = map(lambda x: x * (1 / lena), a) + q = map(lambda x: x * math.sin(phi / 2.0), q) + q.append(math.cos(phi / 2.0)) + return q + +def build_rotmatrix(q): + m = (GLdouble * 16)() + m[0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]) + m[1] = 2.0 * (q[0] * q[1] - q[2] * q[3]) + m[2] = 2.0 * (q[2] * q[0] + q[1] * q[3]) + m[3] = 0.0 + + m[4] = 2.0 * (q[0] * q[1] + q[2] * q[3]) + m[5] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]) + m[6] = 2.0 * (q[1] * q[2] - q[0] * q[3]) + m[7] = 0.0 + + m[8] = 2.0 * (q[2] * q[0] - q[1] * q[3]) + m[9] = 2.0 * (q[1] * q[2] + q[0] * q[3]) + m[10] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]) + m[11] = 0.0 + + m[12] = 0.0 + m[13] = 0.0 + m[14] = 0.0 + m[15] = 1.0 + return m + + +def project_to_sphere(r, x, y): + d = math.sqrt(x * x + y * y) + if (d < r * 0.70710678118654752440): + return math.sqrt(r * r - d * d) + else: + t = r / 1.41421356237309504880 + return t * t / d + + +def mulquat(q1, rq): + return [q1[3] * rq[0] + q1[0] * rq[3] + q1[1] * rq[2] - q1[2] * rq[1], + q1[3] * rq[1] + q1[1] * rq[3] + q1[2] * rq[0] - q1[0] * rq[2], + q1[3] * rq[2] + q1[2] * rq[3] + q1[0] * rq[1] - q1[1] * rq[0], + q1[3] * rq[3] - q1[0] * rq[0] - q1[1] * rq[1] - q1[2] * rq[2]] diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/__init__.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,309 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import logging + +try: + import wx +except: + logging.error(_("WX is not installed. This program requires WX to run.")) + raise + +from printrun.utils import install_locale +install_locale('pronterface') + +from .controls import ControlsSizer, add_extra_controls +from .viz import VizPane +from .log import LogPane +from .toolbar import MainToolbar + +class ToggleablePane(wx.BoxSizer): + + def __init__(self, root, label, parentpanel, parentsizers): + super(ToggleablePane, self).__init__(wx.HORIZONTAL) + if not parentpanel: parentpanel = root.panel + self.root = root + self.visible = True + self.parentpanel = parentpanel + self.parentsizers = parentsizers + self.panepanel = root.newPanel(parentpanel) + self.button = wx.Button(parentpanel, -1, label, size = (22, 18), style = wx.BU_EXACTFIT) + self.button.Bind(wx.EVT_BUTTON, self.toggle) + + def toggle(self, event): + if self.visible: + self.Hide(self.panepanel) + self.on_hide() + else: + self.Show(self.panepanel) + self.on_show() + self.visible = not self.visible + self.button.SetLabel(">" if self.button.GetLabel() == "<" else "<") + +class LeftPaneToggleable(ToggleablePane): + def __init__(self, root, parentpanel, parentsizers): + super(LeftPaneToggleable, self).__init__(root, "<", parentpanel, parentsizers) + self.Add(self.panepanel, 0, wx.EXPAND) + self.Add(self.button, 0) + + def set_sizer(self, sizer): + self.panepanel.SetSizer(sizer) + + def on_show(self): + for sizer in self.parentsizers: + sizer.Layout() + + def on_hide(self): + for sizer in self.parentsizers: + # Expand right splitterwindow + if isinstance(sizer, wx.SplitterWindow): + if sizer.shrinked: + button_width = self.button.GetSize()[0] + sizer.SetSashPosition(sizer.GetSize()[0] - button_width) + else: + sizer.Layout() + +class LogPaneToggleable(ToggleablePane): + def __init__(self, root, parentpanel, parentsizers): + super(LogPaneToggleable, self).__init__(root, ">", parentpanel, parentsizers) + self.Add(self.button, 0) + pane = LogPane(root, self.panepanel) + self.panepanel.SetSizer(pane) + self.Add(self.panepanel, 1, wx.EXPAND) + self.splitter = self.parentpanel.GetParent() + + def on_show(self): + self.splitter.shrinked = False + self.splitter.SetSashPosition(self.splitter.GetSize()[0] - self.orig_width) + self.splitter.SetMinimumPaneSize(self.orig_min_size) + self.splitter.SetSashGravity(self.orig_gravity) + if hasattr(self.splitter, "SetSashSize"): self.splitter.SetSashSize(self.orig_sash_size) + if hasattr(self.splitter, "SetSashInvisible"): self.splitter.SetSashInvisible(False) + for sizer in self.parentsizers: + sizer.Layout() + + def on_hide(self): + self.splitter.shrinked = True + self.orig_width = self.splitter.GetSize()[0] - self.splitter.GetSashPosition() + button_width = self.button.GetSize()[0] + self.orig_min_size = self.splitter.GetMinimumPaneSize() + self.orig_gravity = self.splitter.GetSashGravity() + self.splitter.SetMinimumPaneSize(button_width) + self.splitter.SetSashGravity(1) + self.splitter.SetSashPosition(self.splitter.GetSize()[0] - button_width) + if hasattr(self.splitter, "SetSashSize"): + self.orig_sash_size = self.splitter.GetSashSize() + self.splitter.SetSashSize(0) + if hasattr(self.splitter, "SetSashInvisible"): self.splitter.SetSashInvisible(True) + for sizer in self.parentsizers: + sizer.Layout() + +class MainWindow(wx.Frame): + + def __init__(self, *args, **kwargs): + super(MainWindow, self).__init__(*args, **kwargs) + # this list will contain all controls that should be only enabled + # when we're connected to a printer + self.panel = wx.Panel(self, -1) + self.reset_ui() + self.statefulControls = [] + + def reset_ui(self): + self.panels = [] + self.printerControls = [] + + def newPanel(self, parent, add_to_list = True): + panel = wx.Panel(parent) + self.registerPanel(panel, add_to_list) + return panel + + def registerPanel(self, panel, add_to_list = True): + panel.SetBackgroundColour(self.bgcolor) + if add_to_list: self.panels.append(panel) + + def createTabbedGui(self): + self.notesizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = wx.Notebook(self.panel) + self.notebook.SetBackgroundColour(self.bgcolor) + page1panel = self.newPanel(self.notebook) + page2panel = self.newPanel(self.notebook) + self.mainsizer_page1 = wx.BoxSizer(wx.VERTICAL) + page1panel1 = self.newPanel(page1panel) + page1panel2 = self.newPanel(page1panel) + self.toolbarsizer = MainToolbar(self, page1panel1, use_wrapsizer = True) + page1panel1.SetSizer(self.toolbarsizer) + self.mainsizer_page1.Add(page1panel1, 0, wx.EXPAND) + self.lowersizer = wx.BoxSizer(wx.HORIZONTAL) + page1panel2.SetSizer(self.lowersizer) + leftsizer = wx.BoxSizer(wx.VERTICAL) + controls_sizer = ControlsSizer(self, page1panel2, True) + leftsizer.Add(controls_sizer, 1, wx.ALIGN_CENTER) + rightsizer = wx.BoxSizer(wx.VERTICAL) + extracontrols = wx.GridBagSizer() + add_extra_controls(extracontrols, self, page1panel2, controls_sizer.extra_buttons) + rightsizer.AddStretchSpacer() + rightsizer.Add(extracontrols, 0, wx.ALIGN_CENTER) + self.lowersizer.Add(leftsizer, 0, wx.ALIGN_CENTER | wx.RIGHT, border = 10) + self.lowersizer.Add(rightsizer, 1, wx.ALIGN_CENTER) + self.mainsizer_page1.Add(page1panel2, 1) + self.mainsizer = wx.BoxSizer(wx.HORIZONTAL) + self.splitterwindow = wx.SplitterWindow(page2panel, style = wx.SP_3D) + page2sizer1 = wx.BoxSizer(wx.HORIZONTAL) + page2panel1 = self.newPanel(self.splitterwindow) + page2sizer2 = wx.BoxSizer(wx.HORIZONTAL) + page2panel2 = self.newPanel(self.splitterwindow) + vizpane = VizPane(self, page2panel1) + page2sizer1.Add(vizpane, 1, wx.EXPAND) + page2sizer2.Add(LogPane(self, page2panel2), 1, wx.EXPAND) + page2panel1.SetSizer(page2sizer1) + page2panel2.SetSizer(page2sizer2) + self.splitterwindow.SetMinimumPaneSize(1) + self.splitterwindow.SetSashGravity(0.5) + self.splitterwindow.SplitVertically(page2panel1, page2panel2, + self.settings.last_sash_position) + self.mainsizer.Add(self.splitterwindow, 1, wx.EXPAND) + page1panel.SetSizer(self.mainsizer_page1) + page2panel.SetSizer(self.mainsizer) + self.notesizer.Add(self.notebook, 1, wx.EXPAND) + self.notebook.AddPage(page1panel, _("Commands")) + self.notebook.AddPage(page2panel, _("Status")) + if self.settings.uimode == _("Tabbed with platers"): + from printrun.stlplater import StlPlaterPanel + from printrun.gcodeplater import GcodePlaterPanel + page3panel = StlPlaterPanel(parent = self.notebook, + callback = self.platecb, + build_dimensions = self.build_dimensions_list, + circular_platform = self.settings.circular_bed, + simarrange_path = self.settings.simarrange_path, + antialias_samples = int(self.settings.antialias3dsamples)) + page4panel = GcodePlaterPanel(parent = self.notebook, + callback = self.platecb, + build_dimensions = self.build_dimensions_list, + circular_platform = self.settings.circular_bed, + antialias_samples = int(self.settings.antialias3dsamples)) + self.registerPanel(page3panel) + self.registerPanel(page4panel) + self.notebook.AddPage(page3panel, _("Plater")) + self.notebook.AddPage(page4panel, _("G-Code Plater")) + self.panel.SetSizer(self.notesizer) + self.panel.Bind(wx.EVT_MOUSE_EVENTS, self.editbutton) + self.Bind(wx.EVT_CLOSE, self.kill) + + # Custom buttons + if wx.VERSION > (2, 9): self.cbuttonssizer = wx.WrapSizer(wx.HORIZONTAL) + else: self.cbuttonssizer = wx.GridBagSizer() + self.cbuttonssizer = wx.GridBagSizer() + self.centerpanel = self.newPanel(page1panel2) + self.centerpanel.SetSizer(self.cbuttonssizer) + rightsizer.Add(self.centerpanel, 0, wx.ALIGN_CENTER) + rightsizer.AddStretchSpacer() + + self.panel.SetSizerAndFit(self.notesizer) + + self.cbuttons_reload() + minsize = self.lowersizer.GetMinSize() # lower pane + minsize[1] = self.notebook.GetSize()[1] + self.SetMinSize(self.ClientToWindowSize(minsize)) # client to window + self.Fit() + + def createGui(self, compact = False, mini = False): + self.mainsizer = wx.BoxSizer(wx.VERTICAL) + self.lowersizer = wx.BoxSizer(wx.HORIZONTAL) + upperpanel = self.newPanel(self.panel, False) + self.toolbarsizer = MainToolbar(self, upperpanel) + lowerpanel = self.newPanel(self.panel) + upperpanel.SetSizer(self.toolbarsizer) + lowerpanel.SetSizer(self.lowersizer) + leftpanel = self.newPanel(lowerpanel) + left_pane = LeftPaneToggleable(self, leftpanel, [self.lowersizer]) + leftpanel.SetSizer(left_pane) + left_real_panel = left_pane.panepanel + controls_panel = self.newPanel(left_real_panel) + controls_sizer = ControlsSizer(self, controls_panel, mini_mode = mini) + controls_panel.SetSizer(controls_sizer) + left_sizer = wx.BoxSizer(wx.VERTICAL) + left_sizer.Add(controls_panel, 1, wx.EXPAND) + left_pane.set_sizer(left_sizer) + self.lowersizer.Add(leftpanel, 0, wx.EXPAND) + if not compact: # Use a splitterwindow to group viz and log + rightpanel = self.newPanel(lowerpanel) + rightsizer = wx.BoxSizer(wx.VERTICAL) + rightpanel.SetSizer(rightsizer) + self.splitterwindow = wx.SplitterWindow(rightpanel, style = wx.SP_3D) + self.splitterwindow.SetMinimumPaneSize(150) + self.splitterwindow.SetSashGravity(0.8) + rightsizer.Add(self.splitterwindow, 1, wx.EXPAND) + vizpanel = self.newPanel(self.splitterwindow) + logpanel = self.newPanel(self.splitterwindow) + self.splitterwindow.SplitVertically(vizpanel, logpanel, + self.settings.last_sash_position) + self.splitterwindow.shrinked = False + else: + vizpanel = self.newPanel(lowerpanel) + logpanel = self.newPanel(left_real_panel) + viz_pane = VizPane(self, vizpanel) + # Custom buttons + if wx.VERSION > (2, 9): self.cbuttonssizer = wx.WrapSizer(wx.HORIZONTAL) + else: self.cbuttonssizer = wx.GridBagSizer() + self.centerpanel = self.newPanel(vizpanel) + self.centerpanel.SetSizer(self.cbuttonssizer) + viz_pane.Add(self.centerpanel, 0, flag = wx.ALIGN_CENTER) + vizpanel.SetSizer(viz_pane) + if compact: + log_pane = LogPane(self, logpanel) + else: + log_pane = LogPaneToggleable(self, logpanel, [self.lowersizer]) + left_pane.parentsizers.append(self.splitterwindow) + logpanel.SetSizer(log_pane) + if not compact: + self.lowersizer.Add(rightpanel, 1, wx.EXPAND) + else: + left_sizer.Add(logpanel, 1, wx.EXPAND) + self.lowersizer.Add(vizpanel, 1, wx.EXPAND) + self.mainsizer.Add(upperpanel, 0, wx.EXPAND) + self.mainsizer.Add(lowerpanel, 1, wx.EXPAND) + self.panel.SetSizer(self.mainsizer) + self.panel.Bind(wx.EVT_MOUSE_EVENTS, self.editbutton) + self.Bind(wx.EVT_CLOSE, self.kill) + + self.mainsizer.Layout() + # This prevents resizing below a reasonnable value + # We sum the lowersizer (left pane / viz / log) min size + # the toolbar height and the statusbar/menubar sizes + minsize = [0, 0] + minsize[0] = self.lowersizer.GetMinSize()[0] # lower pane + minsize[1] = max(viz_pane.GetMinSize()[1], controls_sizer.GetMinSize()[1]) + minsize[1] += self.toolbarsizer.GetMinSize()[1] # toolbar height + displaysize = wx.DisplaySize() + minsize[0] = min(minsize[0], displaysize[0]) + minsize[1] = min(minsize[1], displaysize[1]) + self.SetMinSize(self.ClientToWindowSize(minsize)) # client to window + + self.cbuttons_reload() + + def gui_set_connected(self): + self.xyb.enable() + self.zb.enable() + for control in self.printerControls: + control.Enable() + + def gui_set_disconnected(self): + self.printbtn.Disable() + self.pausebtn.Disable() + self.recoverbtn.Disable() + for control in self.printerControls: + control.Disable() + self.xyb.disable() + self.zb.disable() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/bufferedcanvas.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/bufferedcanvas.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,111 @@ +""" +BufferedCanvas -- flicker-free canvas widget +Copyright (C) 2005, 2006 Daniel Keep, 2011 Duane Johnson + +To use this widget, just override or replace the draw method. +This will be called whenever the widget size changes, or when +the update method is explicitly called. + +Please submit any improvements/bugfixes/ideas to the following +url: + + http://wiki.wxpython.org/index.cgi/BufferedCanvas + +2006-04-29: Added bugfix for a crash on Mac provided by Marc Jans. +""" + +# Hint: try removing '.sp4msux0rz' +__author__ = 'Daniel Keep ' + +__license__ = """ +This file is part of the Printrun suite. + +Printrun is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Printrun is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Printrun. If not, see . +""" + +__all__ = ['BufferedCanvas'] + +import wx + +class BufferedCanvas(wx.Panel): + """ + Implements a flicker-free canvas widget. + + Standard usage is to subclass this class, and override the + draw method. The draw method is passed a device context, which + should be used to do your drawing. + + If you want to force a redraw (for whatever reason), you should + call the update method. This is because the draw method is never + called as a result of an EVT_PAINT event. + """ + + # These are our two buffers. Just be aware that when the buffers + # are flipped, the REFERENCES are swapped. So I wouldn't want to + # try holding onto explicit references to one or the other ;) + buffer = None + backbuffer = None + + def __init__(self, + parent, + ID=-1, + pos = wx.DefaultPosition, + size = wx.DefaultSize, + style = wx.NO_FULL_REPAINT_ON_RESIZE | wx.WANTS_CHARS): + wx.Panel.__init__(self, parent, ID, pos, size, style) + + # Bind events + self.Bind(wx.EVT_PAINT, self.onPaint) + + # Disable background erasing (flicker-licious) + def disable_event(*pargs, **kwargs): + pass # the sauce, please + self.Bind(wx.EVT_ERASE_BACKGROUND, disable_event) + + # + # General methods + # + + def draw(self, dc, w, h): + """ + Stub: called when the canvas needs to be re-drawn. + """ + pass + + def update(self): + """ + Causes the canvas to be updated. + """ + self.Refresh() + + def getWidthHeight(self): + width, height = self.GetClientSizeTuple() + if width == 0: + width = 1 + if height == 0: + height = 1 + return (width, height) + + # + # Event handlers + # + + def onPaint(self, event): + # Blit the front buffer to the screen + w, h = self.GetClientSizeTuple() + if not w or not h: + return + else: + dc = wx.BufferedPaintDC(self) + self.draw(dc, w, h) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/controls.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/controls.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,453 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx + +from .xybuttons import XYButtons, XYButtonsMini +from .zbuttons import ZButtons, ZButtonsMini +from .graph import Graph +from .widgets import TempGauge +from wx.lib.agw.floatspin import FloatSpin + +from .utils import make_button, make_custom_button + +class XYZControlsSizer(wx.GridBagSizer): + + def __init__(self, root, parentpanel = None): + super(XYZControlsSizer, self).__init__() + if not parentpanel: parentpanel = root.panel + root.xyb = XYButtons(parentpanel, root.moveXY, root.homeButtonClicked, root.spacebarAction, root.bgcolor, zcallback=root.moveZ) + self.Add(root.xyb, pos = (0, 1), flag = wx.ALIGN_CENTER) + root.zb = ZButtons(parentpanel, root.moveZ, root.bgcolor) + self.Add(root.zb, pos = (0, 2), flag = wx.ALIGN_CENTER) + wx.CallAfter(root.xyb.SetFocus) + +def add_extra_controls(self, root, parentpanel, extra_buttons = None, mini_mode = False): + standalone_mode = extra_buttons is not None + base_line = 1 if standalone_mode else 2 + + if standalone_mode: + gauges_base_line = base_line + 10 + elif mini_mode and root.display_graph: + gauges_base_line = base_line + 7 + else: + gauges_base_line = base_line + 6 + tempdisp_line = gauges_base_line + (2 if root.display_gauges else 0) + if mini_mode and root.display_graph: + e_base_line = base_line + 3 + else: + e_base_line = base_line + 2 + + pos_mapping = { + "htemp_label": (base_line + 0, 0), + "htemp_off": (base_line + 0, 2), + "htemp_val": (base_line + 0, 3), + "htemp_set": (base_line + 0, 4), + "btemp_label": (base_line + 1, 0), + "btemp_off": (base_line + 1, 2), + "btemp_val": (base_line + 1, 3), + "btemp_set": (base_line + 1, 4), + "ebuttons": (e_base_line + 0, 0), + "esettings": (e_base_line + 1, 0), + "speedcontrol": (e_base_line + 2, 0), + "flowcontrol": (e_base_line + 3, 0), + "htemp_gauge": (gauges_base_line + 0, 0), + "btemp_gauge": (gauges_base_line + 1, 0), + "tempdisp": (tempdisp_line, 0), + "extrude": (3, 0), + "reverse": (3, 2), + } + + span_mapping = { + "htemp_label": (1, 2), + "htemp_off": (1, 1), + "htemp_val": (1, 1), + "htemp_set": (1, 1 if root.display_graph else 2), + "btemp_label": (1, 2), + "btemp_off": (1, 1), + "btemp_val": (1, 1), + "btemp_set": (1, 1 if root.display_graph else 2), + "ebuttons": (1, 5 if root.display_graph else 6), + "esettings": (1, 5 if root.display_graph else 6), + "speedcontrol": (1, 5 if root.display_graph else 6), + "flowcontrol": (1, 5 if root.display_graph else 6), + "htemp_gauge": (1, 5 if mini_mode else 6), + "btemp_gauge": (1, 5 if mini_mode else 6), + "tempdisp": (1, 5 if mini_mode else 6), + "extrude": (1, 2), + "reverse": (1, 3), + } + + if standalone_mode: + pos_mapping["tempgraph"] = (base_line + 5, 0) + span_mapping["tempgraph"] = (5, 6) + elif mini_mode: + pos_mapping["tempgraph"] = (base_line + 2, 0) + span_mapping["tempgraph"] = (1, 5) + else: + pos_mapping["tempgraph"] = (base_line + 0, 5) + span_mapping["tempgraph"] = (5, 1) + + if mini_mode: + pos_mapping["etool_label"] = (0, 0) + pos_mapping["etool_val"] = (0, 1) + pos_mapping["edist_label"] = (0, 2) + pos_mapping["edist_val"] = (0, 3) + pos_mapping["edist_unit"] = (0, 4) + else: + pos_mapping["edist_label"] = (0, 0) + pos_mapping["edist_val"] = (1, 0) + pos_mapping["edist_unit"] = (1, 1) + pos_mapping["efeed_label"] = (0, 2) + pos_mapping["efeed_val"] = (1, 2) + pos_mapping["efeed_unit"] = (1, 3) + + def add(name, widget, *args, **kwargs): + kwargs["pos"] = pos_mapping[name] + if name in span_mapping: + kwargs["span"] = span_mapping[name] + if "container" in kwargs: + container = kwargs["container"] + del kwargs["container"] + else: + container = self + container.Add(widget, *args, **kwargs) + + # Hotend & bed temperatures # + + # Hotend temp + add("htemp_label", wx.StaticText(parentpanel, -1, _("Heat:")), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + htemp_choices = [root.temps[i] + " (" + i + ")" for i in sorted(root.temps.keys(), key = lambda x:root.temps[x])] + + root.settoff = make_button(parentpanel, _("Off"), lambda e: root.do_settemp("off"), _("Switch Hotend Off"), size = (38, -1), style = wx.BU_EXACTFIT) + root.printerControls.append(root.settoff) + add("htemp_off", root.settoff) + + if root.settings.last_temperature not in map(float, root.temps.values()): + htemp_choices = [str(root.settings.last_temperature)] + htemp_choices + root.htemp = wx.ComboBox(parentpanel, -1, choices = htemp_choices, + style = wx.CB_DROPDOWN, size = (80, -1)) + root.htemp.SetToolTip(wx.ToolTip(_("Select Temperature for Hotend"))) + root.htemp.Bind(wx.EVT_COMBOBOX, root.htemp_change) + + add("htemp_val", root.htemp) + root.settbtn = make_button(parentpanel, _("Set"), root.do_settemp, _("Switch Hotend On"), size = (38, -1), style = wx.BU_EXACTFIT) + root.printerControls.append(root.settbtn) + add("htemp_set", root.settbtn, flag = wx.EXPAND) + + # Bed temp + add("btemp_label", wx.StaticText(parentpanel, -1, _("Bed:")), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + btemp_choices = [root.bedtemps[i] + " (" + i + ")" for i in sorted(root.bedtemps.keys(), key = lambda x:root.temps[x])] + + root.setboff = make_button(parentpanel, _("Off"), lambda e: root.do_bedtemp("off"), _("Switch Heated Bed Off"), size = (38, -1), style = wx.BU_EXACTFIT) + root.printerControls.append(root.setboff) + add("btemp_off", root.setboff) + + if root.settings.last_bed_temperature not in map(float, root.bedtemps.values()): + btemp_choices = [str(root.settings.last_bed_temperature)] + btemp_choices + root.btemp = wx.ComboBox(parentpanel, -1, choices = btemp_choices, + style = wx.CB_DROPDOWN, size = (80, -1)) + root.btemp.SetToolTip(wx.ToolTip(_("Select Temperature for Heated Bed"))) + root.btemp.Bind(wx.EVT_COMBOBOX, root.btemp_change) + add("btemp_val", root.btemp) + + root.setbbtn = make_button(parentpanel, _("Set"), root.do_bedtemp, _("Switch Heated Bed On"), size = (38, -1), style = wx.BU_EXACTFIT) + root.printerControls.append(root.setbbtn) + add("btemp_set", root.setbbtn, flag = wx.EXPAND) + + root.btemp.SetValue(str(root.settings.last_bed_temperature)) + root.htemp.SetValue(str(root.settings.last_temperature)) + + # added for an error where only the bed would get (pla) or (abs). + # This ensures, if last temp is a default pla or abs, it will be marked so. + # if it is not, then a (user) remark is added. This denotes a manual entry + + for i in btemp_choices: + if i.split()[0] == str(root.settings.last_bed_temperature).split('.')[0] or i.split()[0] == str(root.settings.last_bed_temperature): + root.btemp.SetValue(i) + for i in htemp_choices: + if i.split()[0] == str(root.settings.last_temperature).split('.')[0] or i.split()[0] == str(root.settings.last_temperature): + root.htemp.SetValue(i) + + if '(' not in root.btemp.Value: + root.btemp.SetValue(root.btemp.Value + ' (user)') + if '(' not in root.htemp.Value: + root.htemp.SetValue(root.htemp.Value + ' (user)') + + # Speed control # + speedpanel = root.newPanel(parentpanel) + speedsizer = wx.BoxSizer(wx.HORIZONTAL) + speedsizer.Add(wx.StaticText(speedpanel, -1, _("Print speed:")), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + + root.speed_slider = wx.Slider(speedpanel, -1, 100, 1, 300) + speedsizer.Add(root.speed_slider, 1, flag = wx.EXPAND) + + root.speed_spin = FloatSpin(speedpanel, -1, value = 100, min_val = 1, max_val = 300, digits = 0, style = wx.ALIGN_LEFT, size = (80, -1)) + speedsizer.Add(root.speed_spin, 0, flag = wx.ALIGN_CENTER_VERTICAL) + root.speed_label = wx.StaticText(speedpanel, -1, _("%")) + speedsizer.Add(root.speed_label, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + + def speedslider_set(event): + root.do_setspeed() + root.speed_setbtn.SetBackgroundColour(wx.NullColour) + root.speed_setbtn = make_button(speedpanel, _("Set"), speedslider_set, _("Set print speed factor"), size = (38, -1), style = wx.BU_EXACTFIT) + root.printerControls.append(root.speed_setbtn) + speedsizer.Add(root.speed_setbtn, flag = wx.ALIGN_CENTER) + speedpanel.SetSizer(speedsizer) + add("speedcontrol", speedpanel, flag = wx.EXPAND) + + def speedslider_spin(event): + value = root.speed_spin.GetValue() + root.speed_setbtn.SetBackgroundColour("red") + root.speed_slider.SetValue(value) + root.speed_spin.Bind(wx.EVT_SPINCTRL, speedslider_spin) + + def speedslider_scroll(event): + value = root.speed_slider.GetValue() + root.speed_setbtn.SetBackgroundColour("red") + root.speed_spin.SetValue(value) + root.speed_slider.Bind(wx.EVT_SCROLL, speedslider_scroll) + + # Flow control # + flowpanel = root.newPanel(parentpanel) + flowsizer = wx.BoxSizer(wx.HORIZONTAL) + flowsizer.Add(wx.StaticText(flowpanel, -1, _("Print flow:")), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + + root.flow_slider = wx.Slider(flowpanel, -1, 100, 1, 300) + flowsizer.Add(root.flow_slider, 1, flag = wx.EXPAND) + + root.flow_spin = FloatSpin(flowpanel, -1, value = 100, min_val = 1, max_val = 300, digits = 0, style = wx.ALIGN_LEFT, size = (60, -1)) + flowsizer.Add(root.flow_spin, 0, flag = wx.ALIGN_CENTER_VERTICAL) + root.flow_label = wx.StaticText(flowpanel, -1, _("%")) + flowsizer.Add(root.flow_label, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + + def flowslider_set(event): + root.do_setflow() + root.flow_setbtn.SetBackgroundColour(wx.NullColour) + root.flow_setbtn = make_button(flowpanel, _("Set"), flowslider_set, _("Set print flow factor"), size = (38, -1), style = wx.BU_EXACTFIT) + root.printerControls.append(root.flow_setbtn) + flowsizer.Add(root.flow_setbtn, flag = wx.ALIGN_CENTER) + flowpanel.SetSizer(flowsizer) + add("flowcontrol", flowpanel, flag = wx.EXPAND) + + def flowslider_spin(event): + value = root.flow_spin.GetValue() + root.flow_setbtn.SetBackgroundColour("red") + root.flow_slider.SetValue(value) + root.flow_spin.Bind(wx.EVT_SPINCTRL, flowslider_spin) + + def flowslider_scroll(event): + value = root.flow_slider.GetValue() + root.flow_setbtn.SetBackgroundColour("red") + root.flow_spin.SetValue(value) + root.flow_slider.Bind(wx.EVT_SCROLL, flowslider_scroll) + + # Temperature gauges # + + if root.display_gauges: + root.hottgauge = TempGauge(parentpanel, size = (-1, 24), title = _("Heater:"), maxval = 300, bgcolor = root.bgcolor) + add("htemp_gauge", root.hottgauge, flag = wx.EXPAND) + root.bedtgauge = TempGauge(parentpanel, size = (-1, 24), title = _("Bed:"), maxval = 150, bgcolor = root.bgcolor) + add("btemp_gauge", root.bedtgauge, flag = wx.EXPAND) + + def hotendgauge_scroll_setpoint(e): + rot = e.GetWheelRotation() + if rot > 0: + root.do_settemp(str(root.hsetpoint + 1)) + elif rot < 0: + root.do_settemp(str(max(0, root.hsetpoint - 1))) + + def bedgauge_scroll_setpoint(e): + rot = e.GetWheelRotation() + if rot > 0: + root.do_settemp(str(root.bsetpoint + 1)) + elif rot < 0: + root.do_settemp(str(max(0, root.bsetpoint - 1))) + root.hottgauge.Bind(wx.EVT_MOUSEWHEEL, hotendgauge_scroll_setpoint) + root.bedtgauge.Bind(wx.EVT_MOUSEWHEEL, bedgauge_scroll_setpoint) + + # Temperature (M105) feedback display # + root.tempdisp = wx.StaticText(parentpanel, -1, "", style = wx.ST_NO_AUTORESIZE) + + def on_tempdisp_size(evt): + root.tempdisp.Wrap(root.tempdisp.GetSize().width) + root.tempdisp.Bind(wx.EVT_SIZE, on_tempdisp_size) + + def tempdisp_setlabel(label): + wx.StaticText.SetLabel(root.tempdisp, label) + root.tempdisp.Wrap(root.tempdisp.GetSize().width) + root.tempdisp.SetSize((-1, root.tempdisp.GetBestSize().height)) + root.tempdisp.SetLabel = tempdisp_setlabel + add("tempdisp", root.tempdisp, flag = wx.EXPAND) + + # Temperature graph # + + if root.display_graph: + root.graph = Graph(parentpanel, wx.ID_ANY, root) + add("tempgraph", root.graph, flag = wx.EXPAND | wx.ALL, border = 5) + root.graph.Bind(wx.EVT_LEFT_DOWN, root.graph.show_graph_window) + + # Extrusion controls # + + # Extrusion settings + esettingspanel = root.newPanel(parentpanel) + esettingssizer = wx.GridBagSizer() + esettingssizer.SetEmptyCellSize((0, 0)) + root.edist = FloatSpin(esettingspanel, -1, value = root.settings.last_extrusion, min_val = 0, max_val = 1000, size = (90, -1), digits = 1) + root.edist.SetBackgroundColour((225, 200, 200)) + root.edist.SetForegroundColour("black") + root.edist.Bind(wx.EVT_SPINCTRL, root.setfeeds) + root.edist.Bind(wx.EVT_TEXT, root.setfeeds) + add("edist_label", wx.StaticText(esettingspanel, -1, _("Length:")), container = esettingssizer, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, border = 5) + add("edist_val", root.edist, container = esettingssizer, flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5) + unit_label = _("mm") if mini_mode else _("mm @") + add("edist_unit", wx.StaticText(esettingspanel, -1, unit_label), container = esettingssizer, flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5) + root.edist.SetToolTip(wx.ToolTip(_("Amount to Extrude or Retract (mm)"))) + if not mini_mode: + root.efeedc = FloatSpin(esettingspanel, -1, value = root.settings.e_feedrate, min_val = 0, max_val = 50000, size = (90, -1), digits = 1) + root.efeedc.SetToolTip(wx.ToolTip(_("Extrude / Retract speed (mm/min)"))) + root.efeedc.SetBackgroundColour((225, 200, 200)) + root.efeedc.SetForegroundColour("black") + root.efeedc.Bind(wx.EVT_SPINCTRL, root.setfeeds) + root.efeedc.Bind(wx.EVT_TEXT, root.setfeeds) + add("efeed_val", root.efeedc, container = esettingssizer, flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5) + add("efeed_label", wx.StaticText(esettingspanel, -1, _("Speed:")), container = esettingssizer, flag = wx.ALIGN_LEFT) + add("efeed_unit", wx.StaticText(esettingspanel, -1, _("mm/\nmin")), container = esettingssizer, flag = wx.ALIGN_CENTER) + else: + root.efeedc = None + esettingspanel.SetSizer(esettingssizer) + add("esettings", esettingspanel, flag = wx.ALIGN_LEFT) + + if not standalone_mode: + ebuttonspanel = root.newPanel(parentpanel) + ebuttonssizer = wx.BoxSizer(wx.HORIZONTAL) + if root.settings.extruders > 1: + etool_sel_panel = esettingspanel if mini_mode else ebuttonspanel + etool_label = wx.StaticText(etool_sel_panel, -1, _("Tool:")) + if root.settings.extruders == 2: + root.extrudersel = wx.Button(etool_sel_panel, -1, "0", style = wx.BU_EXACTFIT) + root.extrudersel.SetToolTip(wx.ToolTip(_("Click to switch current extruder"))) + + def extrudersel_cb(event): + if root.extrudersel.GetLabel() == "1": + new = "0" + else: + new = "1" + root.extrudersel.SetLabel(new) + root.tool_change(event) + root.extrudersel.Bind(wx.EVT_BUTTON, extrudersel_cb) + root.extrudersel.GetValue = root.extrudersel.GetLabel + root.extrudersel.SetValue = root.extrudersel.SetLabel + else: + choices = [str(i) for i in range(0, root.settings.extruders)] + root.extrudersel = wx.ComboBox(etool_sel_panel, -1, choices = choices, + style = wx.CB_DROPDOWN | wx.CB_READONLY, + size = (50, -1)) + root.extrudersel.SetToolTip(wx.ToolTip(_("Select current extruder"))) + root.extrudersel.SetValue(choices[0]) + root.extrudersel.Bind(wx.EVT_COMBOBOX, root.tool_change) + root.printerControls.append(root.extrudersel) + if mini_mode: + add("etool_label", etool_label, container = esettingssizer, flag = wx.ALIGN_CENTER) + add("etool_val", root.extrudersel, container = esettingssizer) + else: + ebuttonssizer.Add(etool_label, flag = wx.ALIGN_CENTER) + ebuttonssizer.Add(root.extrudersel) + + for key in ["extrude", "reverse"]: + desc = root.cpbuttons[key] + btn = make_custom_button(root, ebuttonspanel, desc, + style = wx.BU_EXACTFIT) + ebuttonssizer.Add(btn, 1, flag = wx.EXPAND) + + ebuttonspanel.SetSizer(ebuttonssizer) + add("ebuttons", ebuttonspanel, flag = wx.EXPAND) + else: + for key, btn in extra_buttons.items(): + add(key, btn, flag = wx.EXPAND) + +class ControlsSizer(wx.GridBagSizer): + + def __init__(self, root, parentpanel = None, standalone_mode = False, mini_mode = False): + super(ControlsSizer, self).__init__() + if not parentpanel: parentpanel = root.panel + if mini_mode: self.make_mini(root, parentpanel) + else: self.make_standard(root, parentpanel, standalone_mode) + + def make_standard(self, root, parentpanel, standalone_mode): + lltspanel = root.newPanel(parentpanel) + llts = wx.BoxSizer(wx.HORIZONTAL) + lltspanel.SetSizer(llts) + self.Add(lltspanel, pos = (0, 0), span = (1, 6)) + xyzpanel = root.newPanel(parentpanel) + self.xyzsizer = XYZControlsSizer(root, xyzpanel) + xyzpanel.SetSizer(self.xyzsizer) + self.Add(xyzpanel, pos = (1, 0), span = (1, 6), flag = wx.ALIGN_CENTER) + + self.extra_buttons = {} + pos_mapping = {"extrude": (4, 0), + "reverse": (4, 2), + } + span_mapping = {"extrude": (1, 2), + "reverse": (1, 3), + } + for key, desc in root.cpbuttons.items(): + if not standalone_mode and key in ["extrude", "reverse"]: + continue + panel = lltspanel if key == "motorsoff" else parentpanel + btn = make_custom_button(root, panel, desc) + if key == "motorsoff": + llts.Add(btn) + elif not standalone_mode: + self.Add(btn, pos = pos_mapping[key], span = span_mapping[key], flag = wx.EXPAND) + else: + self.extra_buttons[key] = btn + + root.xyfeedc = wx.SpinCtrl(lltspanel, -1, str(root.settings.xy_feedrate), min = 0, max = 50000, size = (97, -1)) + root.xyfeedc.SetToolTip(wx.ToolTip(_("Set Maximum Speed for X & Y axes (mm/min)"))) + llts.Add(wx.StaticText(lltspanel, -1, _("XY:")), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL) + llts.Add(root.xyfeedc) + llts.Add(wx.StaticText(lltspanel, -1, _("mm/min Z:")), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL) + root.zfeedc = wx.SpinCtrl(lltspanel, -1, str(root.settings.z_feedrate), min = 0, max = 50000, size = (90, -1)) + root.zfeedc.SetToolTip(wx.ToolTip(_("Set Maximum Speed for Z axis (mm/min)"))) + llts.Add(root.zfeedc,) + + root.xyfeedc.Bind(wx.EVT_SPINCTRL, root.setfeeds) + root.zfeedc.Bind(wx.EVT_SPINCTRL, root.setfeeds) + root.xyfeedc.Bind(wx.EVT_TEXT, root.setfeeds) + root.zfeedc.Bind(wx.EVT_TEXT, root.setfeeds) + root.zfeedc.SetBackgroundColour((180, 255, 180)) + root.zfeedc.SetForegroundColour("black") + + if not standalone_mode: + add_extra_controls(self, root, parentpanel, None) + + def make_mini(self, root, parentpanel): + root.xyb = XYButtonsMini(parentpanel, root.moveXY, root.homeButtonClicked, + root.spacebarAction, root.bgcolor, + zcallback = root.moveZ) + self.Add(root.xyb, pos = (1, 0), span = (1, 4), flag = wx.ALIGN_CENTER) + root.zb = ZButtonsMini(parentpanel, root.moveZ, root.bgcolor) + self.Add(root.zb, pos = (0, 4), span = (2, 1), flag = wx.ALIGN_CENTER) + wx.CallAfter(root.xyb.SetFocus) + + pos_mapping = {"motorsoff": (0, 0), + } + span_mapping = {"motorsoff": (1, 4), + } + btn = make_custom_button(root, parentpanel, root.cpbuttons["motorsoff"]) + self.Add(btn, pos = pos_mapping["motorsoff"], span = span_mapping["motorsoff"], flag = wx.EXPAND) + + add_extra_controls(self, root, parentpanel, None, True) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/graph.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/graph.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,452 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx +from math import log10, floor, ceil + +from printrun.utils import install_locale +install_locale('pronterface') + +from .bufferedcanvas import BufferedCanvas + +class GraphWindow(wx.Frame): + def __init__(self, root, parent_graph = None, size = (600, 600)): + super(GraphWindow, self).__init__(None, title = _("Temperature graph"), + size = size) + panel = wx.Panel(self, -1) + vbox = wx.BoxSizer(wx.VERTICAL) + self.graph = Graph(panel, wx.ID_ANY, root, parent_graph = parent_graph) + vbox.Add(self.graph, 1, wx.EXPAND) + panel.SetSizer(vbox) + +class Graph(BufferedCanvas): + '''A class to show a Graph with Pronterface.''' + + def __init__(self, parent, id, root, pos = wx.DefaultPosition, + size = wx.Size(150, 80), style = 0, parent_graph = None): + # Forcing a no full repaint to stop flickering + style = style | wx.NO_FULL_REPAINT_ON_RESIZE + super(Graph, self).__init__(parent, id, pos, size, style) + self.root = root + + if parent_graph is not None: + self.extruder0temps = parent_graph.extruder0temps + self.extruder0targettemps = parent_graph.extruder0targettemps + self.extruder1temps = parent_graph.extruder1temps + self.extruder1targettemps = parent_graph.extruder1targettemps + self.bedtemps = parent_graph.bedtemps + self.bedtargettemps = parent_graph.bedtargettemps + self.fanpowers=parent_graph.fanpowers + else: + self.extruder0temps = [0] + self.extruder0targettemps = [0] + self.extruder1temps = [0] + self.extruder1targettemps = [0] + self.bedtemps = [0] + self.bedtargettemps = [0] + self.fanpowers= [0] + + self.timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.updateTemperatures, self.timer) + + self.minyvalue = 0 + self.maxyvalue = 260 + self.rescaley = True # should the Y axis be rescaled dynamically? + if self.rescaley: + self._ybounds = Graph._YBounds(self) + + # If rescaley is set then ybars gives merely an estimate + # Note that "bars" actually indicate the number of internal+external gridlines. + self.ybars = 5 + self.xbars = 7 # One bar per 10 second + self.xsteps = 60 # Covering 1 minute in the graph + + self.window = None + + def show_graph_window(self, event = None): + if not self.window: + self.window = GraphWindow(self.root, self) + self.window.Show() + if self.timer.IsRunning(): + self.window.graph.StartPlotting(self.timer.Interval) + else: + self.window.Raise() + + def __del__(self): + if self.window: self.window.Close() + + def updateTemperatures(self, event): + self.AddBedTemperature(self.bedtemps[-1]) + self.AddBedTargetTemperature(self.bedtargettemps[-1]) + self.AddExtruder0Temperature(self.extruder0temps[-1]) + self.AddExtruder0TargetTemperature(self.extruder0targettemps[-1]) + self.AddExtruder1Temperature(self.extruder1temps[-1]) + self.AddExtruder1TargetTemperature(self.extruder1targettemps[-1]) + self.AddFanPower(self.fanpowers[-1]) + if self.rescaley: + self._ybounds.update() + self.Refresh() + + def drawgrid(self, dc, gc): + # cold, medium, hot = wx.Colour(0, 167, 223),\ + # wx.Colour(239, 233, 119),\ + # wx.Colour(210, 50.100) + # col1 = wx.Colour(255, 0, 0, 255) + # col2 = wx.Colour(255, 255, 255, 128) + + # b = gc.CreateLinearGradientBrush(0, 0, w, h, col1, col2) + + gc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 0), 1)) + + # gc.SetBrush(wx.Brush(wx.Colour(245, 245, 255, 52))) + + # gc.SetBrush(gc.CreateBrush(wx.Brush(wx.Colour(0, 0, 0, 255)))) + gc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 255), 1)) + + # gc.DrawLines(wx.Point(0, 0), wx.Point(50, 10)) + + font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD) + gc.SetFont(font, wx.Colour(23, 44, 44)) + + # draw vertical bars + dc.SetPen(wx.Pen(wx.Colour(225, 225, 225), 1)) + for x in range(self.xbars + 1): + dc.DrawLine(x * (float(self.width - 1) / (self.xbars - 1)), + 0, + x * (float(self.width - 1) / (self.xbars - 1)), + self.height) + + # draw horizontal bars + spacing = self._calculate_spacing() # spacing between bars, in degrees + yspan = self.maxyvalue - self.minyvalue + ybars = int(yspan / spacing) # Should be close to self.ybars + firstbar = int(ceil(self.minyvalue / spacing)) # in degrees + dc.SetPen(wx.Pen(wx.Colour(225, 225, 225), 1)) + for y in range(firstbar, firstbar + ybars + 1): + # y_pos = y*(float(self.height)/self.ybars) + degrees = y * spacing + y_pos = self._y_pos(degrees) + dc.DrawLine(0, y_pos, self.width, y_pos) + gc.DrawText(unicode(y * spacing), + 1, y_pos - (font.GetPointSize() / 2)) + + if self.timer.IsRunning() is False: + font = wx.Font(14, wx.DEFAULT, wx.NORMAL, wx.BOLD) + gc.SetFont(font, wx.Colour(3, 4, 4)) + gc.DrawText("Graph offline", + self.width / 2 - (font.GetPointSize() * 3), + self.height / 2 - (font.GetPointSize() * 1)) + + # dc.DrawCircle(50, 50, 1) + + # gc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 0), 1)) + # gc.DrawLines([[20, 30], [10, 53]]) + # dc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 0), 1)) + + def _y_pos(self, temperature): + """Converts a temperature, in degrees, to a pixel position""" + # fraction of the screen from the bottom + frac = (float(temperature - self.minyvalue) + / (self.maxyvalue - self.minyvalue)) + return int((1.0 - frac) * (self.height - 1)) + + def _calculate_spacing(self): + # Allow grids of spacings 1,2.5,5,10,25,50,100,etc + + yspan = float(self.maxyvalue - self.minyvalue) + log_yspan = log10(yspan / self.ybars) + exponent = int(floor(log_yspan)) + + # calculate boundary points between allowed spacings + log1_25 = log10(2) + log10(1) + log10(2.5) - log10(1 + 2.5) + log25_5 = log10(2) + log10(2.5) + log10(5) - log10(2.5 + 5) + log5_10 = log10(2) + log10(5) + log10(10) - log10(5 + 10) + + if log_yspan - exponent < log1_25: + return 10 ** exponent + elif log1_25 <= log_yspan - exponent < log25_5: + return 25 * 10 ** (exponent - 1) + elif log25_5 <= log_yspan - exponent < log5_10: + return 5 * 10 ** exponent + else: + return 10 ** (exponent + 1) + + def drawtemperature(self, dc, gc, temperature_list, + text, text_xoffset, r, g, b, a): + if self.timer.IsRunning() is False: + dc.SetPen(wx.Pen(wx.Colour(128, 128, 128, 128), 1)) + else: + dc.SetPen(wx.Pen(wx.Colour(r, g, b, a), 1)) + + x_add = float(self.width) / self.xsteps + x_pos = 0.0 + lastxvalue = 0.0 + lastyvalue = temperature_list[-1] + + for temperature in (temperature_list): + y_pos = self._y_pos(temperature) + if (x_pos > 0.0): # One need 2 points to draw a line. + dc.DrawLine(lastxvalue, lastyvalue, x_pos, y_pos) + + lastxvalue = x_pos + x_pos = float(x_pos) + x_add + lastyvalue = y_pos + + if len(text) > 0: + font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.BOLD) + # font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL) + if self.timer.IsRunning() is False: + gc.SetFont(font, wx.Colour(128, 128, 128)) + else: + gc.SetFont(font, wx.Colour(r, g, b)) + + text_size = len(text) * text_xoffset + 1 + gc.DrawText(text, + x_pos - x_add - (font.GetPointSize() * text_size), + lastyvalue - (font.GetPointSize() / 2)) + + def drawfanpower(self, dc, gc): + self.drawtemperature(dc, gc, self.fanpowers, + "Fan", 1, 0, 0, 0, 128) + + def drawbedtemp(self, dc, gc): + self.drawtemperature(dc, gc, self.bedtemps, + "Bed", 2, 255, 0, 0, 128) + + def drawbedtargettemp(self, dc, gc): + self.drawtemperature(dc, gc, self.bedtargettemps, + "Bed Target", 2, 255, 120, 0, 128) + + def drawextruder0temp(self, dc, gc): + self.drawtemperature(dc, gc, self.extruder0temps, + "Ex0", 1, 0, 155, 255, 128) + + def drawextruder0targettemp(self, dc, gc): + self.drawtemperature(dc, gc, self.extruder0targettemps, + "Ex0 Target", 2, 0, 5, 255, 128) + + def drawextruder1temp(self, dc, gc): + self.drawtemperature(dc, gc, self.extruder1temps, + "Ex1", 3, 55, 55, 0, 128) + + def drawextruder1targettemp(self, dc, gc): + self.drawtemperature(dc, gc, self.extruder1targettemps, + "Ex1 Target", 2, 55, 55, 0, 128) + + def SetFanPower(self, value): + self.fanpowers.pop() + self.fanpowers.append(value) + + def AddFanPower(self, value): + self.fanpowers.append(value) + if float(len(self.fanpowers) - 1) / self.xsteps > 1: + self.fanpowers.pop(0) + + def SetBedTemperature(self, value): + self.bedtemps.pop() + self.bedtemps.append(value) + + def AddBedTemperature(self, value): + self.bedtemps.append(value) + if float(len(self.bedtemps) - 1) / self.xsteps > 1: + self.bedtemps.pop(0) + + def SetBedTargetTemperature(self, value): + self.bedtargettemps.pop() + self.bedtargettemps.append(value) + + def AddBedTargetTemperature(self, value): + self.bedtargettemps.append(value) + if float(len(self.bedtargettemps) - 1) / self.xsteps > 1: + self.bedtargettemps.pop(0) + + def SetExtruder0Temperature(self, value): + self.extruder0temps.pop() + self.extruder0temps.append(value) + + def AddExtruder0Temperature(self, value): + self.extruder0temps.append(value) + if float(len(self.extruder0temps) - 1) / self.xsteps > 1: + self.extruder0temps.pop(0) + + def SetExtruder0TargetTemperature(self, value): + self.extruder0targettemps.pop() + self.extruder0targettemps.append(value) + + def AddExtruder0TargetTemperature(self, value): + self.extruder0targettemps.append(value) + if float(len(self.extruder0targettemps) - 1) / self.xsteps > 1: + self.extruder0targettemps.pop(0) + + def SetExtruder1Temperature(self, value): + self.extruder1temps.pop() + self.extruder1temps.append(value) + + def AddExtruder1Temperature(self, value): + self.extruder1temps.append(value) + if float(len(self.extruder1temps) - 1) / self.xsteps > 1: + self.extruder1temps.pop(0) + + def SetExtruder1TargetTemperature(self, value): + self.extruder1targettemps.pop() + self.extruder1targettemps.append(value) + + def AddExtruder1TargetTemperature(self, value): + self.extruder1targettemps.append(value) + if float(len(self.extruder1targettemps) - 1) / self.xsteps > 1: + self.extruder1targettemps.pop(0) + + def StartPlotting(self, time): + self.Refresh() + self.timer.Start(time) + if self.window: self.window.graph.StartPlotting(time) + + def StopPlotting(self): + self.timer.Stop() + self.Refresh() + if self.window: self.window.graph.StopPlotting() + + def draw(self, dc, w, h): + dc.SetBackground(wx.Brush(self.root.bgcolor)) + dc.Clear() + gc = wx.GraphicsContext.Create(dc) + self.width = w + self.height = h + self.drawgrid(dc, gc) + self.drawbedtargettemp(dc, gc) + self.drawbedtemp(dc, gc) + self.drawfanpower(dc, gc) + self.drawextruder0targettemp(dc, gc) + self.drawextruder0temp(dc, gc) + self.drawextruder1targettemp(dc, gc) + self.drawextruder1temp(dc, gc) + + class _YBounds(object): + """Small helper class to claculate y bounds dynamically""" + + def __init__(self, graph, minimum_scale=5.0, buffer=0.10): + """_YBounds(Graph,float,float) + + graph parent object to calculate scales for + minimum_scale minimum range to show on the graph + buffer amount of padding to add above & below the + displayed temperatures. Given as a fraction of the + total range. (Eg .05 to use 90% of the range for + temperatures) + """ + self.graph = graph + self.min_scale = minimum_scale + self.buffer = buffer + + # Frequency to rescale the graph + self.update_freq = 10 + # number of updates since last full refresh + self._last_update = self.update_freq + + def update(self, forceUpdate=False): + """Updates graph.minyvalue and graph.maxyvalue based on current + temperatures """ + self._last_update += 1 + # TODO Smart update. Only do full calculation every 10s. Otherwise, + # just look at current graph & expand if necessary + if forceUpdate or self._last_update >= self.update_freq: + self.graph.minyvalue, self.graph.maxyvalue = self.getBounds() + self._last_update = 0 + else: + bounds = self.getBoundsQuick() + self.graph.minyvalue, self.graph.maxyvalue = bounds + + def getBounds(self): + """ + Calculates the bounds based on the current temperatures + + Rules: + * Include the full extruder0 history + * Include the current target temp (but not necessarily old + settings) + * Include the extruder1 and/or bed temp if + 1) The target temp is >0 + 2) The history has ever been above 5 + * Include at least min_scale + * Include at least buffer above & below the extreme temps + """ + extruder0_min = min(self.graph.extruder0temps) + extruder0_max = max(self.graph.extruder0temps) + extruder0_target = self.graph.extruder0targettemps[-1] + extruder1_min = min(self.graph.extruder1temps) + extruder1_max = max(self.graph.extruder1temps) + extruder1_target = self.graph.extruder1targettemps[-1] + bed_min = min(self.graph.bedtemps) + bed_max = max(self.graph.bedtemps) + bed_target = self.graph.bedtargettemps[-1] + + miny = min(extruder0_min, extruder0_target) + maxy = max(extruder0_max, extruder0_target) + if extruder1_target > 0 or extruder1_max > 5: # use extruder1 + miny = min(miny, extruder1_min, extruder1_target) + maxy = max(maxy, extruder1_max, extruder1_target) + if bed_target > 0 or bed_max > 5: # use HBP + miny = min(miny, bed_min, bed_target) + maxy = max(maxy, bed_max, bed_target) + miny=min(0,miny); + maxy=max(260,maxy); + + padding = (maxy - miny) * self.buffer / (1.0 - 2 * self.buffer) + miny -= padding + maxy += padding + + if maxy - miny < self.min_scale: + extrapadding = (self.min_scale - maxy + miny) / 2.0 + miny -= extrapadding + maxy += extrapadding + + return (miny, maxy) + + def getBoundsQuick(self): + # Only look at current temps + extruder0_min = self.graph.extruder0temps[-1] + extruder0_max = self.graph.extruder0temps[-1] + extruder0_target = self.graph.extruder0targettemps[-1] + extruder1_min = self.graph.extruder1temps[-1] + extruder1_max = self.graph.extruder1temps[-1] + extruder1_target = self.graph.extruder1targettemps[-1] + bed_min = self.graph.bedtemps[-1] + bed_max = self.graph.bedtemps[-1] + bed_target = self.graph.bedtargettemps[-1] + + miny = min(extruder0_min, extruder0_target) + maxy = max(extruder0_max, extruder0_target) + if extruder1_target > 0 or extruder1_max > 5: # use extruder1 + miny = min(miny, extruder1_min, extruder1_target) + maxy = max(maxy, extruder1_max, extruder1_target) + if bed_target > 0 or bed_max > 5: # use HBP + miny = min(miny, bed_min, bed_target) + maxy = max(maxy, bed_max, bed_target) + miny=min(0,miny); + maxy=max(260,maxy); + + # We have to rescale, so add padding + bufratio = self.buffer / (1.0 - self.buffer) + if miny < self.graph.minyvalue: + padding = (self.graph.maxyvalue - miny) * bufratio + miny -= padding + if maxy > self.graph.maxyvalue: + padding = (maxy - self.graph.minyvalue) * bufratio + maxy += padding + + return (min(miny, self.graph.minyvalue), + max(maxy, self.graph.maxyvalue)) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/log.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/log.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,40 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx + +from .utils import make_button + +class LogPane(wx.BoxSizer): + + def __init__(self, root, parentpanel = None): + super(LogPane, self).__init__(wx.VERTICAL) + if not parentpanel: parentpanel = root.panel + root.logbox = wx.TextCtrl(parentpanel, style = wx.TE_MULTILINE, size = (350, -1)) + root.logbox.SetMinSize((100, -1)) + root.logbox.SetEditable(0) + self.Add(root.logbox, 1, wx.EXPAND) + bottom_panel = root.newPanel(parentpanel) + lbrs = wx.BoxSizer(wx.HORIZONTAL) + root.commandbox = wx.TextCtrl(bottom_panel, style = wx.TE_PROCESS_ENTER) + root.commandbox.SetToolTip(wx.ToolTip(_("Send commands to printer\n(Type 'help' for simple\nhelp function)"))) + root.commandbox.Bind(wx.EVT_TEXT_ENTER, root.sendline) + root.commandbox.Bind(wx.EVT_CHAR, root.cbkey) + root.commandbox.history = [u""] + root.commandbox.histindex = 1 + lbrs.Add(root.commandbox, 1) + root.sendbtn = make_button(bottom_panel, _("Send"), root.sendline, _("Send Command to Printer"), style = wx.BU_EXACTFIT, container = lbrs) + bottom_panel.SetSizer(lbrs) + self.Add(bottom_panel, 0, wx.EXPAND) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/toolbar.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/toolbar.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,94 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx + +from .utils import make_autosize_button + +def MainToolbar(root, parentpanel = None, use_wrapsizer = False): + if not parentpanel: parentpanel = root.panel + if root.settings.lockbox: + root.locker = wx.CheckBox(parentpanel, label = _("Lock") + " ") + root.locker.Bind(wx.EVT_CHECKBOX, root.lock) + root.locker.SetToolTip(wx.ToolTip(_("Lock graphical interface"))) + glob = wx.BoxSizer(wx.HORIZONTAL) + parentpanel = root.newPanel(parentpanel) + glob.Add(parentpanel, 1, flag = wx.EXPAND) + glob.Add(root.locker, 0, flag = wx.ALIGN_CENTER) + ToolbarSizer = wx.WrapSizer if use_wrapsizer and wx.VERSION > (2, 9) else wx.BoxSizer + self = ToolbarSizer(wx.HORIZONTAL) + root.rescanbtn = make_autosize_button(parentpanel, _("Port"), root.rescanports, _("Communication Settings\nClick to rescan ports")) + self.Add(root.rescanbtn, 0, wx.TOP | wx.LEFT, 0) + + root.serialport = wx.ComboBox(parentpanel, -1, choices = root.scanserial(), + style = wx.CB_DROPDOWN) + root.serialport.SetToolTip(wx.ToolTip(_("Select Port Printer is connected to"))) + root.rescanports() + self.Add(root.serialport) + + self.Add(wx.StaticText(parentpanel, -1, "@"), 0, wx.RIGHT | wx.ALIGN_CENTER, 0) + root.baud = wx.ComboBox(parentpanel, -1, + choices = ["2400", "9600", "19200", "38400", + "57600", "115200", "250000"], + style = wx.CB_DROPDOWN, size = (100, -1)) + root.baud.SetToolTip(wx.ToolTip(_("Select Baud rate for printer communication"))) + try: + root.baud.SetValue("115200") + root.baud.SetValue(str(root.settings.baudrate)) + except: + pass + self.Add(root.baud) + + if not hasattr(root, "connectbtn"): + root.connectbtn = make_autosize_button(parentpanel, _("Connect"), root.connect, _("Connect to the printer")) + root.statefulControls.append(root.connectbtn) + else: + root.connectbtn.Reparent(parentpanel) + self.Add(root.connectbtn) + if not hasattr(root, "resetbtn"): + root.resetbtn = make_autosize_button(parentpanel, _("Reset"), root.reset, _("Reset the printer")) + root.statefulControls.append(root.resetbtn) + else: + root.resetbtn.Reparent(parentpanel) + self.Add(root.resetbtn) + + self.AddStretchSpacer(prop = 1) + + root.loadbtn = make_autosize_button(parentpanel, _("Load file"), root.loadfile, _("Load a 3D model file"), self) + root.sdbtn = make_autosize_button(parentpanel, _("SD"), root.sdmenu, _("SD Card Printing"), self) + root.sdbtn.Reparent(parentpanel) + root.printerControls.append(root.sdbtn) + if not hasattr(root, "printbtn"): + root.printbtn = make_autosize_button(parentpanel, _("Print"), root.printfile, _("Start Printing Loaded File")) + root.statefulControls.append(root.printbtn) + else: + root.printbtn.Reparent(parentpanel) + self.Add(root.printbtn) + if not hasattr(root, "pausebtn"): + root.pausebtn = make_autosize_button(parentpanel, _("Pause"), root.pause, _("Pause Current Print")) + root.statefulControls.append(root.pausebtn) + else: + root.pausebtn.Reparent(parentpanel) + self.Add(root.pausebtn) + root.offbtn = make_autosize_button(parentpanel, _("Off"), root.off, _("Turn printer off"), self) + root.printerControls.append(root.offbtn) + + self.AddStretchSpacer(prop = 4) + + if root.settings.lockbox: + parentpanel.SetSizer(self) + return glob + else: + return self diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/utils.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,37 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx + +def make_button(parent, label, callback, tooltip, container = None, size = wx.DefaultSize, style = 0): + button = wx.Button(parent, -1, label, style = style, size = size) + button.Bind(wx.EVT_BUTTON, callback) + button.SetToolTip(wx.ToolTip(tooltip)) + if container: + container.Add(button) + return button + +def make_autosize_button(*args): + return make_button(*args, size = (-1, -1), style = wx.BU_EXACTFIT) + +def make_custom_button(root, parentpanel, i, style = 0): + btn = make_button(parentpanel, i.label, root.process_button, + i.tooltip, style = style) + btn.SetBackgroundColour(i.background) + btn.SetForegroundColour("black") + btn.properties = i + root.btndict[i.command] = btn + root.printerControls.append(btn) + return btn diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/viz.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/viz.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,109 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import traceback +import logging + +import wx + +class NoViz(object): + + showall = False + + def clear(self, *a): + pass + + def addfile_perlayer(self, gcode, showall = False): + layer_idx = 0 + while layer_idx < len(gcode.all_layers): + yield layer_idx + layer_idx += 1 + yield None + + def addfile(self, *a, **kw): + pass + + def addgcode(self, *a, **kw): + pass + + def addgcodehighlight(self, *a, **kw): + pass + + def Refresh(self, *a): + pass + + def setlayer(self, *a): + pass + +class NoVizWindow(object): + + def __init__(self): + self.p = NoViz() + + def Destroy(self): + pass + +class VizPane(wx.BoxSizer): + + def __init__(self, root, parentpanel = None): + super(VizPane, self).__init__(wx.VERTICAL) + if not parentpanel: parentpanel = root.panel + if root.settings.mainviz == "None": + root.gviz = NoViz() + root.gwindow = NoVizWindow() + return + use2dview = root.settings.mainviz == "2D" + if root.settings.mainviz == "3D": + try: + import printrun.gcview + root.gviz = printrun.gcview.GcodeViewMainWrapper(parentpanel, root.build_dimensions_list, root = root, circular = root.settings.circular_bed, antialias_samples = int(root.settings.antialias3dsamples)) + root.gviz.clickcb = root.show_viz_window + except: + use2dview = True + logging.error("3D view mode requested, but we failed to initialize it.\n" + + "Falling back to 2D view, and here is the backtrace:\n" + + traceback.format_exc()) + if use2dview: + from printrun import gviz + root.gviz = gviz.Gviz(parentpanel, (300, 300), + build_dimensions = root.build_dimensions_list, + grid = (root.settings.preview_grid_step1, root.settings.preview_grid_step2), + extrusion_width = root.settings.preview_extrusion_width, + bgcolor = root.bgcolor) + root.gviz.SetToolTip(wx.ToolTip(_("Click to examine / edit\n layers of loaded file"))) + root.gviz.showall = 1 + root.gviz.Bind(wx.EVT_LEFT_DOWN, root.show_viz_window) + use3dview = root.settings.viz3d + if use3dview: + try: + import printrun.gcview + objects = None + if isinstance(root.gviz, printrun.gcview.GcodeViewMainWrapper): + objects = root.gviz.objects + root.gwindow = printrun.gcview.GcodeViewFrame(None, wx.ID_ANY, 'Gcode view, shift to move view, mousewheel to set layer', size = (600, 600), build_dimensions = root.build_dimensions_list, objects = objects, root = root, circular = root.settings.circular_bed, antialias_samples = int(root.settings.antialias3dsamples)) + except: + use3dview = False + logging.error("3D view mode requested, but we failed to initialize it.\n" + + "Falling back to 2D view, and here is the backtrace:\n" + + traceback.format_exc()) + if not use3dview: + from printrun import gviz + root.gwindow = gviz.GvizWindow(build_dimensions = root.build_dimensions_list, + grid = (root.settings.preview_grid_step1, root.settings.preview_grid_step2), + extrusion_width = root.settings.preview_extrusion_width, + bgcolor = root.bgcolor) + root.gwindow.Bind(wx.EVT_CLOSE, lambda x: root.gwindow.Hide()) + if not isinstance(root.gviz, NoViz): + self.Add(root.gviz.widget, 1, flag = wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/widgets.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/widgets.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,377 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx +import re + +class MacroEditor(wx.Dialog): + """Really simple editor to edit macro definitions""" + + def __init__(self, macro_name, definition, callback, gcode = False): + self.indent_chars = " " + title = " macro %s" + if gcode: + title = " %s" + self.gcode = gcode + wx.Dialog.__init__(self, None, title = title % macro_name, + style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + self.callback = callback + self.panel = wx.Panel(self, -1) + titlesizer = wx.BoxSizer(wx.HORIZONTAL) + self.titletext = wx.StaticText(self.panel, -1, " _") # title%macro_name) + titlesizer.Add(self.titletext, 1) + self.findb = wx.Button(self.panel, -1, _("Find"), style = wx.BU_EXACTFIT) # New button for "Find" (Jezmy) + self.findb.Bind(wx.EVT_BUTTON, self.find) + self.okb = wx.Button(self.panel, -1, _("Save"), style = wx.BU_EXACTFIT) + self.okb.Bind(wx.EVT_BUTTON, self.save) + self.Bind(wx.EVT_CLOSE, self.close) + titlesizer.Add(self.findb) + titlesizer.Add(self.okb) + self.cancelb = wx.Button(self.panel, -1, _("Cancel"), style = wx.BU_EXACTFIT) + self.cancelb.Bind(wx.EVT_BUTTON, self.close) + titlesizer.Add(self.cancelb) + topsizer = wx.BoxSizer(wx.VERTICAL) + topsizer.Add(titlesizer, 0, wx.EXPAND) + self.e = wx.TextCtrl(self.panel, style = wx.HSCROLL | wx.TE_MULTILINE | wx.TE_RICH2, size = (400, 400)) + if not self.gcode: + self.e.SetValue(self.unindent(definition)) + else: + self.e.SetValue("\n".join(definition)) + topsizer.Add(self.e, 1, wx.ALL | wx.EXPAND) + self.panel.SetSizer(topsizer) + topsizer.Layout() + topsizer.Fit(self) + self.Show() + self.e.SetFocus() + + def find(self, ev): + # Ask user what to look for, find it and point at it ... (Jezmy) + S = self.e.GetStringSelection() + if not S: + S = "Z" + FindValue = wx.GetTextFromUser('Please enter a search string:', caption = "Search", default_value = S, parent = None) + somecode = self.e.GetValue() + position = somecode.find(FindValue, self.e.GetInsertionPoint()) + if position == -1: + self.titletext.SetLabel(_("Not Found!")) + else: + self.titletext.SetLabel(str(position)) + + # ananswer = wx.MessageBox(str(numLines)+" Lines detected in file\n"+str(position), "OK") + self.e.SetFocus() + self.e.SetInsertionPoint(position) + self.e.SetSelection(position, position + len(FindValue)) + self.e.ShowPosition(position) + + def ShowMessage(self, ev, message): + dlg = wx.MessageDialog(self, message, + "Info!", wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def save(self, ev): + self.Destroy() + if not self.gcode: + self.callback(self.reindent(self.e.GetValue())) + else: + self.callback(self.e.GetValue().split("\n")) + + def close(self, ev): + self.Destroy() + + def unindent(self, text): + self.indent_chars = text[:len(text) - len(text.lstrip())] + if len(self.indent_chars) == 0: + self.indent_chars = " " + unindented = "" + lines = re.split(r"(?:\r\n?|\n)", text) + if len(lines) <= 1: + return text + for line in lines: + if line.startswith(self.indent_chars): + unindented += line[len(self.indent_chars):] + "\n" + else: + unindented += line + "\n" + return unindented + + def reindent(self, text): + lines = re.split(r"(?:\r\n?|\n)", text) + if len(lines) <= 1: + return text + reindented = "" + for line in lines: + if line.strip() != "": + reindented += self.indent_chars + line + "\n" + return reindented + +SETTINGS_GROUPS = {"Printer": _("Printer settings"), + "UI": _("User interface"), + "Viewer": _("Viewer"), + "Colors": _("Colors"), + "External": _("External commands")} + +class PronterOptionsDialog(wx.Dialog): + """Options editor""" + def __init__(self, pronterface): + wx.Dialog.__init__(self, parent = None, title = _("Edit settings"), + size = (400, 500), style = wx.DEFAULT_DIALOG_STYLE) + panel = wx.Panel(self) + header = wx.StaticBox(panel, label = _("Settings")) + sbox = wx.StaticBoxSizer(header, wx.VERTICAL) + notebook = wx.Notebook(panel) + all_settings = pronterface.settings._all_settings() + group_list = [] + groups = {} + for group in ["Printer", "UI", "Viewer", "Colors", "External"]: + group_list.append(group) + groups[group] = [] + for setting in all_settings: + if setting.group not in group_list: + group_list.append(setting.group) + groups[setting.group] = [] + groups[setting.group].append(setting) + for group in group_list: + grouppanel = wx.Panel(notebook, -1) + notebook.AddPage(grouppanel, SETTINGS_GROUPS[group]) + settings = groups[group] + grid = wx.GridBagSizer(hgap = 8, vgap = 2) + current_row = 0 + for setting in settings: + if setting.name.startswith("separator_"): + sep = wx.StaticLine(grouppanel, size = (-1, 5), style = wx.LI_HORIZONTAL) + grid.Add(sep, pos = (current_row, 0), span = (1, 2), + border = 3, flag = wx.ALIGN_CENTER | wx.ALL | wx.EXPAND) + current_row += 1 + label, widget = setting.get_label(grouppanel), setting.get_widget(grouppanel) + if setting.name.startswith("separator_"): + font = label.GetFont() + font.SetWeight(wx.BOLD) + label.SetFont(font) + grid.Add(label, pos = (current_row, 0), + flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + grid.Add(widget, pos = (current_row, 1), + flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND) + if hasattr(label, "set_default"): + label.Bind(wx.EVT_MOUSE_EVENTS, label.set_default) + if hasattr(widget, "Bind"): + widget.Bind(wx.EVT_MOUSE_EVENTS, label.set_default) + current_row += 1 + grid.AddGrowableCol(1) + grouppanel.SetSizer(grid) + sbox.Add(notebook, 1, wx.EXPAND) + panel.SetSizer(sbox) + topsizer = wx.BoxSizer(wx.VERTICAL) + topsizer.Add(panel, 1, wx.ALL | wx.EXPAND) + topsizer.Add(self.CreateButtonSizer(wx.OK | wx.CANCEL), 0, wx.ALIGN_RIGHT) + self.SetSizerAndFit(topsizer) + self.SetMinSize(self.GetSize()) + +def PronterOptions(pronterface): + dialog = PronterOptionsDialog(pronterface) + if dialog.ShowModal() == wx.ID_OK: + for setting in pronterface.settings._all_settings(): + old_value = setting.value + setting.update() + if setting.value != old_value: + pronterface.set(setting.name, setting.value) + dialog.Destroy() + +class ButtonEdit(wx.Dialog): + """Custom button edit dialog""" + def __init__(self, pronterface): + wx.Dialog.__init__(self, None, title = _("Custom button"), + style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + self.pronterface = pronterface + topsizer = wx.BoxSizer(wx.VERTICAL) + grid = wx.FlexGridSizer(rows = 0, cols = 2, hgap = 4, vgap = 2) + grid.AddGrowableCol(1, 1) + grid.Add(wx.StaticText(self, -1, _("Button title")), 0, wx.BOTTOM | wx.RIGHT) + self.name = wx.TextCtrl(self, -1, "") + grid.Add(self.name, 1, wx.EXPAND) + grid.Add(wx.StaticText(self, -1, _("Command")), 0, wx.BOTTOM | wx.RIGHT) + self.command = wx.TextCtrl(self, -1, "") + xbox = wx.BoxSizer(wx.HORIZONTAL) + xbox.Add(self.command, 1, wx.EXPAND) + self.command.Bind(wx.EVT_TEXT, self.macrob_enabler) + self.macrob = wx.Button(self, -1, "..", style = wx.BU_EXACTFIT) + self.macrob.Bind(wx.EVT_BUTTON, self.macrob_handler) + xbox.Add(self.macrob, 0) + grid.Add(xbox, 1, wx.EXPAND) + grid.Add(wx.StaticText(self, -1, _("Color")), 0, wx.BOTTOM | wx.RIGHT) + self.color = wx.TextCtrl(self, -1, "") + grid.Add(self.color, 1, wx.EXPAND) + topsizer.Add(grid, 0, wx.EXPAND) + topsizer.Add((0, 0), 1) + topsizer.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.ALIGN_CENTER) + self.SetSizer(topsizer) + + def macrob_enabler(self, e): + macro = self.command.GetValue() + valid = False + try: + if macro == "": + valid = True + elif macro in self.pronterface.macros: + valid = True + elif hasattr(self.pronterface.__class__, u"do_" + macro): + valid = False + elif len([c for c in macro if not c.isalnum() and c != "_"]): + valid = False + else: + valid = True + except: + if macro == "": + valid = True + elif macro in self.pronterface.macros: + valid = True + elif len([c for c in macro if not c.isalnum() and c != "_"]): + valid = False + else: + valid = True + self.macrob.Enable(valid) + + def macrob_handler(self, e): + macro = self.command.GetValue() + macro = self.pronterface.edit_macro(macro) + self.command.SetValue(macro) + if self.name.GetValue() == "": + self.name.SetValue(macro) + +class TempGauge(wx.Panel): + + def __init__(self, parent, size = (200, 22), title = "", + maxval = 240, gaugeColour = None, bgcolor = "#FFFFFF"): + wx.Panel.__init__(self, parent, -1, size = size) + self.Bind(wx.EVT_PAINT, self.paint) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.bgcolor = wx.Colour() + self.bgcolor.SetFromName(bgcolor) + self.width, self.height = size + self.title = title + self.max = maxval + self.gaugeColour = gaugeColour + self.value = 0 + self.setpoint = 0 + self.recalc() + + def recalc(self): + mmax = max(int(self.setpoint * 1.05), self.max) + self.scale = float(self.width - 2) / float(mmax) + self.ypt = max(16, int(self.scale * max(self.setpoint, self.max / 6))) + + def SetValue(self, value): + self.value = value + wx.CallAfter(self.Refresh) + + def SetTarget(self, value): + self.setpoint = value + wx.CallAfter(self.Refresh) + + def interpolatedColour(self, val, vmin, vmid, vmax, cmin, cmid, cmax): + if val < vmin: return cmin + if val > vmax: return cmax + if val <= vmid: + lo, hi, val, valhi = cmin, cmid, val - vmin, vmid - vmin + else: + lo, hi, val, valhi = cmid, cmax, val - vmid, vmax - vmid + vv = float(val) / valhi + rgb = lo.Red() + (hi.Red() - lo.Red()) * vv, lo.Green() + (hi.Green() - lo.Green()) * vv, lo.Blue() + (hi.Blue() - lo.Blue()) * vv + rgb = map(lambda x: x * 0.8, rgb) + return wx.Colour(*map(int, rgb)) + + def paint(self, ev): + self.width, self.height = self.GetClientSizeTuple() + self.recalc() + x0, y0, x1, y1, xE, yE = 1, 1, self.ypt + 1, 1, self.width + 1 - 2, 20 + dc = wx.PaintDC(self) + dc.SetBackground(wx.Brush(self.bgcolor)) + dc.Clear() + cold, medium, hot = wx.Colour(0, 167, 223), wx.Colour(239, 233, 119), wx.Colour(210, 50.100) + # gauge1, gauge2 = wx.Colour(255, 255, 210), (self.gaugeColour or wx.Colour(234, 82, 0)) + gauge1 = wx.Colour(255, 255, 210) + shadow1, shadow2 = wx.Colour(110, 110, 110), self.bgcolor + gc = wx.GraphicsContext.Create(dc) + # draw shadow first + # corners + gc.SetBrush(gc.CreateRadialGradientBrush(xE - 7, 9, xE - 7, 9, 8, shadow1, shadow2)) + gc.DrawRectangle(xE - 7, 1, 8, 8) + gc.SetBrush(gc.CreateRadialGradientBrush(xE - 7, 17, xE - 7, 17, 8, shadow1, shadow2)) + gc.DrawRectangle(xE - 7, 17, 8, 8) + gc.SetBrush(gc.CreateRadialGradientBrush(x0 + 6, 17, x0 + 6, 17, 8, shadow1, shadow2)) + gc.DrawRectangle(0, 17, x0 + 6, 8) + # edges + gc.SetBrush(gc.CreateLinearGradientBrush(xE - 6, 0, xE + 1, 0, shadow1, shadow2)) + gc.DrawRectangle(xE - 7, 9, 8, 8) + gc.SetBrush(gc.CreateLinearGradientBrush(x0, yE - 2, x0, yE + 5, shadow1, shadow2)) + gc.DrawRectangle(x0 + 6, yE - 2, xE - 12, 7) + # draw gauge background + gc.SetBrush(gc.CreateLinearGradientBrush(x0, y0, x1 + 1, y1, cold, medium)) + gc.DrawRoundedRectangle(x0, y0, x1 + 4, yE, 6) + gc.SetBrush(gc.CreateLinearGradientBrush(x1 - 2, y1, xE, y1, medium, hot)) + gc.DrawRoundedRectangle(x1 - 2, y1, xE - x1, yE, 6) + # draw gauge + width = 12 + w1 = y0 + 9 - width / 2 + w2 = w1 + width + value = x0 + max(10, min(self.width + 1 - 2, int(self.value * self.scale))) + # gc.SetBrush(gc.CreateLinearGradientBrush(x0, y0 + 3, x0, y0 + 15, gauge1, gauge2)) + # gc.SetBrush(gc.CreateLinearGradientBrush(0, 3, 0, 15, wx.Colour(255, 255, 255), wx.Colour(255, 90, 32))) + gc.SetBrush(gc.CreateLinearGradientBrush(x0, y0 + 3, x0, y0 + 15, gauge1, self.interpolatedColour(value, x0, x1, xE, cold, medium, hot))) + val_path = gc.CreatePath() + val_path.MoveToPoint(x0, w1) + val_path.AddLineToPoint(value, w1) + val_path.AddLineToPoint(value + 2, w1 + width / 4) + val_path.AddLineToPoint(value + 2, w2 - width / 4) + val_path.AddLineToPoint(value, w2) + # val_path.AddLineToPoint(value-4, 10) + val_path.AddLineToPoint(x0, w2) + gc.DrawPath(val_path) + # draw setpoint markers + setpoint = x0 + max(10, int(self.setpoint * self.scale)) + gc.SetBrush(gc.CreateBrush(wx.Brush(wx.Colour(0, 0, 0)))) + setp_path = gc.CreatePath() + setp_path.MoveToPoint(setpoint - 4, y0) + setp_path.AddLineToPoint(setpoint + 4, y0) + setp_path.AddLineToPoint(setpoint, y0 + 5) + setp_path.MoveToPoint(setpoint - 4, yE) + setp_path.AddLineToPoint(setpoint + 4, yE) + setp_path.AddLineToPoint(setpoint, yE - 5) + gc.DrawPath(setp_path) + # draw readout + text = u"T\u00B0 %u/%u" % (self.value, self.setpoint) + # gc.SetFont(gc.CreateFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD), wx.WHITE)) + # gc.DrawText(text, 29,-2) + gc.SetFont(gc.CreateFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD), wx.WHITE)) + gc.DrawText(self.title, x0 + 19, y0 + 4) + gc.DrawText(text, x0 + 119, y0 + 4) + gc.SetFont(gc.CreateFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))) + gc.DrawText(self.title, x0 + 18, y0 + 3) + gc.DrawText(text, x0 + 118, y0 + 3) + +class SpecialButton(object): + + label = None + command = None + background = None + tooltip = None + custom = None + + def __init__(self, label, command, background = None, + tooltip = None, custom = False): + self.label = label + self.command = command + self.background = background + self.tooltip = tooltip + self.custom = custom diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/xybuttons.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/xybuttons.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,501 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx +import math +from .bufferedcanvas import BufferedCanvas +from printrun.utils import imagefile + +def sign(n): + if n < 0: return -1 + elif n > 0: return 1 + else: return 0 + +class XYButtons(BufferedCanvas): + keypad_positions = { + 0: (106, 100), + 1: (86, 83), + 2: (68, 65), + 3: (53, 50) + } + corner_size = (49, 49) + corner_inset = (7, 13) + label_overlay_positions = { + 1: (145, 98.5, 9), + 2: (160.5, 83.5, 10.6), + 3: (178, 66, 13), + 4: (197.3, 46.3, 13.3) + } + concentric_circle_radii = [0, 17, 45, 69, 94, 115] + concentric_inset = 11 + center = (124, 121) + spacer = 7 + imagename = "control_xy.png" + corner_to_axis = { + -1: "center", + 0: "x", + 1: "z", + 2: "y", + 3: "all", + } + + def __init__(self, parent, moveCallback = None, cornerCallback = None, spacebarCallback = None, bgcolor = "#FFFFFF", ID=-1, zcallback=None): + self.bg_bmp = wx.Image(imagefile(self.imagename), wx.BITMAP_TYPE_PNG).ConvertToBitmap() + self.keypad_bmp = wx.Image(imagefile("arrow_keys.png"), wx.BITMAP_TYPE_PNG).ConvertToBitmap() + self.keypad_idx = -1 + self.quadrant = None + self.concentric = None + self.corner = None + self.moveCallback = moveCallback + self.cornerCallback = cornerCallback + self.spacebarCallback = spacebarCallback + self.zCallback = zcallback + self.enabled = False + # Remember the last clicked buttons, so we can repeat when spacebar pressed + self.lastMove = None + self.lastCorner = None + + self.bgcolor = wx.Colour() + self.bgcolor.SetFromName(bgcolor) + self.bgcolormask = wx.Colour(self.bgcolor.Red(), self.bgcolor.Green(), self.bgcolor.Blue(), 128) + + BufferedCanvas.__init__(self, parent, ID, size=self.bg_bmp.GetSize()) + + self.bind_events() + + def bind_events(self): + # Set up mouse and keyboard event capture + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + self.Bind(wx.EVT_KEY_UP, self.OnKey) + wx.GetTopLevelParent(self).Bind(wx.EVT_CHAR_HOOK, self.OnTopLevelKey) + + def disable(self): + self.enabled = False + self.update() + + def enable(self): + self.enabled = True + self.update() + + def repeatLast(self): + if self.lastMove: + self.moveCallback(*self.lastMove) + if self.lastCorner: + self.cornerCallback(self.corner_to_axis[self.lastCorner]) + + def clearRepeat(self): + self.lastMove = None + self.lastCorner = None + + def distanceToLine(self, pos, x1, y1, x2, y2): + xlen = x2 - x1 + ylen = y2 - y1 + pxlen = x1 - pos.x + pylen = y1 - pos.y + return abs(xlen * pylen - ylen * pxlen) / math.sqrt(xlen ** 2 + ylen ** 2) + + def distanceToPoint(self, x1, y1, x2, y2): + return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) + + def cycleKeypadIndex(self): + idx = self.keypad_idx + 1 + if idx > 2: idx = 0 + return idx + + def setKeypadIndex(self, idx): + self.keypad_idx = idx + self.update() + + def getMovement(self): + xdir = [1, 0, -1, 0, 0, 0][self.quadrant] + ydir = [0, 1, 0, -1, 0, 0][self.quadrant] + zdir = [0, 0, 0, 0, 1, -1][self.quadrant] + magnitude = math.pow(10, self.concentric - 2) + if not zdir == 0: + magnitude = min(magnitude, 10) + return (magnitude * xdir, magnitude * ydir, magnitude * zdir) + + def lookupConcentric(self, radius): + idx = 0 + for r in self.concentric_circle_radii[1:]: + if radius < r: + return idx + idx += 1 + return len(self.concentric_circle_radii) + + def getQuadrantConcentricFromPosition(self, pos): + rel_x = pos[0] - self.center[0] + rel_y = pos[1] - self.center[1] + radius = math.sqrt(rel_x ** 2 + rel_y ** 2) + if rel_x > rel_y and rel_x > -rel_y: + quadrant = 0 # Right + elif rel_x <= rel_y and rel_x > -rel_y: + quadrant = 3 # Down + elif rel_x > rel_y and rel_x < -rel_y: + quadrant = 1 # Up + else: + quadrant = 2 # Left + + idx = self.lookupConcentric(radius) + return (quadrant, idx) + + def mouseOverKeypad(self, mpos): + for idx, kpos in self.keypad_positions.items(): + radius = self.distanceToPoint(mpos[0], mpos[1], kpos[0], kpos[1]) + if radius < 9: + return idx + return None + + def drawPartialPie(self, gc, center, r1, r2, angle1, angle2): + p1 = wx.Point(center.x + r1 * math.cos(angle1), center.y + r1 * math.sin(angle1)) + + path = gc.CreatePath() + path.MoveToPoint(p1.x, p1.y) + path.AddArc(center.x, center.y, r1, angle1, angle2, True) + path.AddArc(center.x, center.y, r2, angle2, angle1, False) + path.AddLineToPoint(p1.x, p1.y) + gc.DrawPath(path) + + def highlightQuadrant(self, gc, quadrant, concentric): + assert(quadrant >= 0 and quadrant <= 3) + assert(concentric >= 0 and concentric <= 4) + + inner_ring_radius = self.concentric_inset + # fudge = math.pi*0.002 + fudge = -0.02 + center = wx.Point(self.center[0], self.center[1]) + if quadrant == 0: + a1, a2 = (-math.pi * 0.25, math.pi * 0.25) + center.x += inner_ring_radius + elif quadrant == 1: + a1, a2 = (math.pi * 1.25, math.pi * 1.75) + center.y -= inner_ring_radius + elif quadrant == 2: + a1, a2 = (math.pi * 0.75, math.pi * 1.25) + center.x -= inner_ring_radius + elif quadrant == 3: + a1, a2 = (math.pi * 0.25, math.pi * 0.75) + center.y += inner_ring_radius + + r1 = self.concentric_circle_radii[concentric] + r2 = self.concentric_circle_radii[concentric + 1] + + self.drawPartialPie(gc, center, r1 - inner_ring_radius, r2 - inner_ring_radius, a1 + fudge, a2 - fudge) + + def drawCorner(self, gc, x, y, angle = 0.0): + w, h = self.corner_size + + gc.PushState() + gc.Translate(x, y) + gc.Rotate(angle) + path = gc.CreatePath() + path.MoveToPoint(-w / 2, -h / 2) + path.AddLineToPoint(w / 2, -h / 2) + path.AddLineToPoint(w / 2, -h / 2 + h / 4) + path.AddLineToPoint(w / 12, h / 12) + path.AddLineToPoint(-w / 2 + w / 4, h / 2) + path.AddLineToPoint(-w / 2, h / 2) + path.AddLineToPoint(-w / 2, -h / 2) + gc.DrawPath(path) + gc.PopState() + + def highlightCorner(self, gc, corner = 0): + w, h = self.corner_size + xinset, yinset = self.corner_inset + cx, cy = self.center + ww, wh = self.GetSizeTuple() + + if corner == 0: + x, y = (cx - ww / 2 + xinset + 1, cy - wh / 2 + yinset) + self.drawCorner(gc, x + w / 2, y + h / 2, 0) + elif corner == 1: + x, y = (cx + ww / 2 - xinset, cy - wh / 2 + yinset) + self.drawCorner(gc, x - w / 2, y + h / 2, math.pi / 2) + elif corner == 2: + x, y = (cx + ww / 2 - xinset, cy + wh / 2 - yinset - 1) + self.drawCorner(gc, x - w / 2, y - h / 2, math.pi) + elif corner == 3: + x, y = (cx - ww / 2 + xinset + 1, cy + wh / 2 - yinset - 1) + self.drawCorner(gc, x + w / 2, y - h / 2, math.pi * 3 / 2) + + def drawCenteredDisc(self, gc, radius): + cx, cy = self.center + gc.DrawEllipse(cx - radius, cy - radius, radius * 2, radius * 2) + + def draw(self, dc, w, h): + dc.SetBackground(wx.Brush(self.bgcolor)) + dc.Clear() + gc = wx.GraphicsContext.Create(dc) + + if self.bg_bmp: + w, h = (self.bg_bmp.GetWidth(), self.bg_bmp.GetHeight()) + gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) + + if self.enabled and self.IsEnabled(): + # Brush and pen for grey overlay when mouse hovers over + gc.SetPen(wx.Pen(wx.Colour(100, 100, 100, 172), 4)) + gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 128))) + + if self.concentric is not None: + if self.concentric < len(self.concentric_circle_radii): + if self.concentric == 0: + self.drawCenteredDisc(gc, self.concentric_circle_radii[1]) + elif self.quadrant is not None: + self.highlightQuadrant(gc, self.quadrant, self.concentric) + elif self.corner is not None: + self.highlightCorner(gc, self.corner) + + if self.keypad_idx >= 0: + padw, padh = (self.keypad_bmp.GetWidth(), self.keypad_bmp.GetHeight()) + pos = self.keypad_positions[self.keypad_idx] + pos = (pos[0] - padw / 2 - 3, pos[1] - padh / 2 - 3) + gc.DrawBitmap(self.keypad_bmp, pos[0], pos[1], padw, padh) + + # Draw label overlays + gc.SetPen(wx.Pen(wx.Colour(255, 255, 255, 128), 1)) + gc.SetBrush(wx.Brush(wx.Colour(255, 255, 255, 128 + 64))) + for idx, kpos in self.label_overlay_positions.items(): + if idx != self.concentric: + r = kpos[2] + gc.DrawEllipse(kpos[0] - r, kpos[1] - r, r * 2, r * 2) + else: + gc.SetPen(wx.Pen(self.bgcolor, 0)) + gc.SetBrush(wx.Brush(self.bgcolormask)) + gc.DrawRectangle(0, 0, w, h) + # Used to check exact position of keypad dots, should we ever resize the bg image + # for idx, kpos in self.label_overlay_positions.items(): + # dc.DrawCircle(kpos[0], kpos[1], kpos[2]) + + # ------ # + # Events # + # ------ # + def OnTopLevelKey(self, evt): + # Let user press escape on any control, and return focus here + if evt.GetKeyCode() == wx.WXK_ESCAPE: + self.SetFocus() + evt.Skip() + + def OnKey(self, evt): + if not self.enabled: + return + if self.keypad_idx >= 0: + if evt.GetKeyCode() == wx.WXK_TAB: + self.setKeypadIndex(self.cycleKeypadIndex()) + elif evt.GetKeyCode() == wx.WXK_UP: + self.quadrant = 1 + elif evt.GetKeyCode() == wx.WXK_DOWN: + self.quadrant = 3 + elif evt.GetKeyCode() == wx.WXK_LEFT: + self.quadrant = 2 + elif evt.GetKeyCode() == wx.WXK_RIGHT: + self.quadrant = 0 + elif evt.GetKeyCode() == wx.WXK_PAGEUP: + self.quadrant = 4 + elif evt.GetKeyCode() == wx.WXK_PAGEDOWN: + self.quadrant = 5 + else: + evt.Skip() + return + + self.concentric = self.keypad_idx + x, y, z = self.getMovement() + + if x != 0 or y != 0 and self.moveCallback: + self.moveCallback(x, y) + if z != 0 and self.zCallback: + self.zCallback(z) + elif evt.GetKeyCode() == wx.WXK_SPACE: + self.spacebarCallback() + + def OnMotion(self, event): + if not self.enabled: + return + + oldcorner = self.corner + oldq, oldc = self.quadrant, self.concentric + + mpos = event.GetPosition() + idx = self.mouseOverKeypad(mpos) + self.quadrant = None + self.concentric = None + if idx is None: + center = wx.Point(self.center[0], self.center[1]) + riseDist = self.distanceToLine(mpos, center.x - 1, center.y - 1, center.x + 1, center.y + 1) + fallDist = self.distanceToLine(mpos, center.x - 1, center.y + 1, center.x + 1, center.y - 1) + self.quadrant, self.concentric = self.getQuadrantConcentricFromPosition(mpos) + + # If mouse hovers in space between quadrants, don't commit to a quadrant + if riseDist <= self.spacer or fallDist <= self.spacer: + self.quadrant = None + + cx, cy = self.center + if mpos.x < cx and mpos.y < cy: + self.corner = 0 + if mpos.x >= cx and mpos.y < cy: + self.corner = 1 + if mpos.x >= cx and mpos.y >= cy: + self.corner = 2 + if mpos.x < cx and mpos.y >= cy: + self.corner = 3 + + if oldq != self.quadrant or oldc != self.concentric or oldcorner != self.corner: + self.update() + + def OnLeftDown(self, event): + if not self.enabled: + return + + # Take focus when clicked so that arrow keys can control movement + self.SetFocus() + + mpos = event.GetPosition() + + idx = self.mouseOverKeypad(mpos) + if idx is None: + self.quadrant, self.concentric = self.getQuadrantConcentricFromPosition(mpos) + if self.concentric is not None: + if self.concentric < len(self.concentric_circle_radii): + if self.concentric == 0: + self.lastCorner = -1 + self.lastMove = None + self.cornerCallback(self.corner_to_axis[-1]) + elif self.quadrant is not None: + x, y, z = self.getMovement() + if self.moveCallback: + self.lastMove = (x, y) + self.lastCorner = None + self.moveCallback(x, y) + elif self.corner is not None: + if self.cornerCallback: + self.lastCorner = self.corner + self.lastMove = None + self.cornerCallback(self.corner_to_axis[self.corner]) + else: + if self.keypad_idx == idx: + self.setKeypadIndex(-1) + else: + self.setKeypadIndex(idx) + + def OnLeaveWindow(self, evt): + self.quadrant = None + self.concentric = None + self.update() + +class XYButtonsMini(XYButtons): + imagename = "control_mini.png" + center = (57, 56.5) + concentric_circle_radii = [0, 30.3] + corner_inset = (5, 5) + corner_size = (50, 50) + outer_radius = 31 + corner_to_axis = { + 0: "x", + 1: "z", + 2: "y", + 3: "center", + } + + def bind_events(self): + # Set up mouse and keyboard event capture + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + + def OnMotion(self, event): + if not self.enabled: + return + + oldcorner = self.corner + oldq, oldc = self.quadrant, self.concentric + + mpos = event.GetPosition() + + self.quadrant, self.concentric = self.getQuadrantConcentricFromPosition(mpos) + + cx, cy = XYButtonsMini.center + if mpos.x < cx and mpos.y < cy: + self.corner = 0 + if mpos.x >= cx and mpos.y < cy: + self.corner = 1 + if mpos.x >= cx and mpos.y >= cy: + self.corner = 2 + if mpos.x < cx and mpos.y >= cy: + self.corner = 3 + + if oldq != self.quadrant or oldc != self.concentric or oldcorner != self.corner: + self.update() + + def OnLeftDown(self, event): + if not self.enabled: + return + + # Take focus when clicked so that arrow keys can control movement + self.SetFocus() + + mpos = event.GetPosition() + + self.quadrant, self.concentric = self.getQuadrantConcentricFromPosition(mpos) + if self.concentric is not None: + if self.concentric < len(self.concentric_circle_radii): + self.cornerCallback("all") + elif self.corner is not None: + if self.cornerCallback: + self.lastCorner = self.corner + self.lastMove = None + self.cornerCallback(self.corner_to_axis[self.corner]) + + def drawCorner(self, gc, x, y, angle = 0.0): + w, h = self.corner_size + + gc.PushState() + gc.Translate(x, y) + gc.Rotate(angle) + path = gc.CreatePath() + path.MoveToPoint(-w / 2, -h / 2) + path.AddLineToPoint(w / 2, -h / 2) + path.AddLineToPoint(w / 2, -h / 2 + h / 4) + path.AddArc(w / 2, h / 2, self.outer_radius, 3 * math.pi / 2, math.pi, False) + path.AddLineToPoint(-w / 2, h / 2) + path.AddLineToPoint(-w / 2, -h / 2) + gc.DrawPath(path) + gc.PopState() + + def draw(self, dc, w, h): + dc.SetBackground(wx.Brush(self.bgcolor)) + dc.Clear() + gc = wx.GraphicsContext.Create(dc) + + if self.bg_bmp: + w, h = (self.bg_bmp.GetWidth(), self.bg_bmp.GetHeight()) + gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) + + if self.enabled and self.IsEnabled(): + # Brush and pen for grey overlay when mouse hovers over + gc.SetPen(wx.Pen(wx.Colour(100, 100, 100, 172), 4)) + gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 128))) + + if self.concentric is not None: + if self.concentric < len(self.concentric_circle_radii): + self.drawCenteredDisc(gc, self.concentric_circle_radii[-1]) + elif self.corner is not None: + self.highlightCorner(gc, self.corner) + else: + gc.SetPen(wx.Pen(self.bgcolor, 0)) + gc.SetBrush(wx.Brush(self.bgcolormask)) + gc.DrawRectangle(0, 0, w, h) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gui/zbuttons.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gui/zbuttons.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,168 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx +from .bufferedcanvas import BufferedCanvas +from printrun.utils import imagefile + +def sign(n): + if n < 0: return -1 + elif n > 0: return 1 + else: return 0 + +class ZButtons(BufferedCanvas): + button_ydistances = [7, 30, 55, 83] # ,112 + move_values = [0.1, 1, 10] + center = (30, 118) + label_overlay_positions = { + 0: (1.1, 18, 9), + 1: (1.1, 41.5, 10.6), + 2: (1.1, 68, 13), + } + imagename = "control_z.png" + + def __init__(self, parent, moveCallback = None, bgcolor = "#FFFFFF", ID=-1): + self.bg_bmp = wx.Image(imagefile(self.imagename), wx.BITMAP_TYPE_PNG).ConvertToBitmap() + self.range = None + self.direction = None + self.orderOfMagnitudeIdx = 0 # 0 means '1', 1 means '10', 2 means '100', etc. + self.moveCallback = moveCallback + self.enabled = False + # Remember the last clicked value, so we can repeat when spacebar pressed + self.lastValue = None + + self.bgcolor = wx.Colour() + self.bgcolor.SetFromName(bgcolor) + self.bgcolormask = wx.Colour(self.bgcolor.Red(), self.bgcolor.Green(), self.bgcolor.Blue(), 128) + + BufferedCanvas.__init__(self, parent, ID, size=self.bg_bmp.GetSize()) + + # Set up mouse and keyboard event capture + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + + def disable(self): + self.enabled = False + self.update() + + def enable(self): + self.enabled = True + self.update() + + def repeatLast(self): + if self.lastValue: + self.moveCallback(self.lastValue) + + def clearRepeat(self): + self.lastValue = None + + def lookupRange(self, ydist): + idx = -1 + for d in self.button_ydistances: + if ydist < d: + return idx + idx += 1 + return None + + def highlight(self, gc, rng, dir): + assert(rng >= -1 and rng <= 3) + assert(dir >= -1 and dir <= 1) + + fudge = 11 + x = 0 + fudge + w = 59 - fudge * 2 + if rng >= 0: + k = 1 if dir > 0 else 0 + y = self.center[1] - (dir * self.button_ydistances[rng + k]) + h = self.button_ydistances[rng + 1] - self.button_ydistances[rng] + gc.DrawRoundedRectangle(x, y, w, h, 4) + # gc.DrawRectangle(x, y, w, h) + # self.drawPartialPie(dc, center, r1-inner_ring_radius, r2-inner_ring_radius, a1+fudge, a2-fudge) + + def getRangeDir(self, pos): + ydelta = self.center[1] - pos[1] + return (self.lookupRange(abs(ydelta)), sign(ydelta)) + + def draw(self, dc, w, h): + dc.SetBackground(wx.Brush(self.bgcolor)) + dc.Clear() + gc = wx.GraphicsContext.Create(dc) + if self.bg_bmp: + w, h = (self.bg_bmp.GetWidth(), self.bg_bmp.GetHeight()) + gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) + + if self.enabled and self.IsEnabled(): + # Draw label overlays + gc.SetPen(wx.Pen(wx.Colour(255, 255, 255, 128), 1)) + gc.SetBrush(wx.Brush(wx.Colour(255, 255, 255, 128 + 64))) + for idx, kpos in self.label_overlay_positions.items(): + if idx != self.range: + r = kpos[2] + gc.DrawEllipse(self.center[0] - kpos[0] - r, self.center[1] - kpos[1] - r, r * 2, r * 2) + + # Top 'layer' is the mouse-over highlights + gc.SetPen(wx.Pen(wx.Colour(100, 100, 100, 172), 4)) + gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 128))) + if self.range is not None and self.direction is not None: + self.highlight(gc, self.range, self.direction) + else: + gc.SetPen(wx.Pen(self.bgcolor, 0)) + gc.SetBrush(wx.Brush(self.bgcolormask)) + gc.DrawRectangle(0, 0, w, h) + + # ------ # + # Events # + # ------ # + + def OnMotion(self, event): + if not self.enabled: + return + + oldr, oldd = self.range, self.direction + + mpos = event.GetPosition() + self.range, self.direction = self.getRangeDir(mpos) + + if oldr != self.range or oldd != self.direction: + self.update() + + def OnLeftDown(self, event): + if not self.enabled: + return + + mpos = event.GetPosition() + r, d = self.getRangeDir(mpos) + if r >= 0: + value = d * self.move_values[r] + if self.moveCallback: + self.lastValue = value + self.moveCallback(value) + + def OnLeaveWindow(self, evt): + self.range = None + self.direction = None + self.update() + +class ZButtonsMini(ZButtons): + button_ydistances = [7, 30, 55] + center = (30, 84) + label_overlay_positions = { + 0: (1, 18, 9), + 1: (1, 42.8, 12.9), + } + imagename = "control_z_mini.png" + move_values = [0.1, 10] diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/gviz.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/gviz.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,554 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +from Queue import Queue +from collections import deque +import numpy +import wx +import time +from . import gcoder +from .injectgcode import injector, injector_edit + +from .utils import imagefile, install_locale, get_home_pos +install_locale('pronterface') + +class GvizBaseFrame(wx.Frame): + + def create_base_ui(self): + self.CreateStatusBar(1) + self.SetStatusText(_("Layer number and Z position show here when you scroll")) + + hpanel = wx.Panel(self, -1) + hbox = wx.BoxSizer(wx.HORIZONTAL) + + panel = wx.Panel(hpanel, -1) + vbox = wx.BoxSizer(wx.VERTICAL) + + vbox = wx.BoxSizer(wx.VERTICAL) + self.toolbar = wx.ToolBar(panel, -1, style = wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_HORZ_TEXT) + self.toolbar.AddSimpleTool(1, wx.Image(imagefile('zoom_in.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), _("Zoom In [+]"), '') + self.toolbar.AddSimpleTool(2, wx.Image(imagefile('zoom_out.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), _("Zoom Out [-]"), '') + self.toolbar.AddSeparator() + self.toolbar.AddSimpleTool(3, wx.Image(imagefile('arrow_up.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), _("Move Up a Layer [U]"), '') + self.toolbar.AddSimpleTool(4, wx.Image(imagefile('arrow_down.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), _("Move Down a Layer [D]"), '') + self.toolbar.AddLabelTool(5, " " + _("Reset view"), wx.Image(imagefile('reset.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), shortHelp = _("Reset view"), longHelp = '') + self.toolbar.AddSeparator() + self.toolbar.AddSimpleTool(6, wx.Image(imagefile('inject.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), shortHelpString = _("Inject G-Code"), longHelpString = _("Insert code at the beginning of this layer")) + self.toolbar.AddSimpleTool(7, wx.Image(imagefile('edit.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), shortHelpString = _("Edit layer"), longHelpString = _("Edit the G-Code of this layer")) + + vbox.Add(self.toolbar, 0, border = 5) + + panel.SetSizer(vbox) + + hbox.Add(panel, 1, flag = wx.EXPAND) + self.layerslider = wx.Slider(hpanel, style = wx.SL_VERTICAL | wx.SL_AUTOTICKS | wx.SL_LEFT | wx.SL_INVERSE) + self.layerslider.Bind(wx.EVT_SCROLL, self.process_slider) + hbox.Add(self.layerslider, 0, border = 5, flag = wx.LEFT | wx.EXPAND) + hpanel.SetSizer(hbox) + + return panel, vbox + + def setlayercb(self, layer): + self.layerslider.SetValue(layer) + + def process_slider(self, event): + raise NotImplementedError + +ID_ABOUT = 101 +ID_EXIT = 110 +class GvizWindow(GvizBaseFrame): + def __init__(self, f = None, size = (600, 600), build_dimensions = [200, 200, 100, 0, 0, 0], grid = (10, 50), extrusion_width = 0.5, bgcolor = "#000000"): + super(GvizWindow, self).__init__(None, title = _("Gcode view, shift to move view, mousewheel to set layer"), size = size) + + panel, vbox = self.create_base_ui() + + self.p = Gviz(panel, size = size, build_dimensions = build_dimensions, grid = grid, extrusion_width = extrusion_width, bgcolor = bgcolor, realparent = self) + + self.toolbar.Realize() + vbox.Add(self.p, 1, wx.EXPAND) + + self.SetMinSize(self.ClientToWindowSize(vbox.GetMinSize())) + self.Bind(wx.EVT_TOOL, lambda x: self.p.zoom(-1, -1, 1.2), id = 1) + self.Bind(wx.EVT_TOOL, lambda x: self.p.zoom(-1, -1, 1 / 1.2), id = 2) + self.Bind(wx.EVT_TOOL, lambda x: self.p.layerup(), id = 3) + self.Bind(wx.EVT_TOOL, lambda x: self.p.layerdown(), id = 4) + self.Bind(wx.EVT_TOOL, self.resetview, id = 5) + self.Bind(wx.EVT_TOOL, lambda x: self.p.inject(), id = 6) + self.Bind(wx.EVT_TOOL, lambda x: self.p.editlayer(), id = 7) + + self.initpos = None + self.p.Bind(wx.EVT_KEY_DOWN, self.key) + self.Bind(wx.EVT_KEY_DOWN, self.key) + self.p.Bind(wx.EVT_MOUSEWHEEL, self.zoom) + self.Bind(wx.EVT_MOUSEWHEEL, self.zoom) + self.p.Bind(wx.EVT_MOUSE_EVENTS, self.mouse) + self.Bind(wx.EVT_MOUSE_EVENTS, self.mouse) + + if f: + gcode = gcoder.GCode(f, get_home_pos(self.p.build_dimensions)) + self.p.addfile(gcode) + + def set_current_gline(self, gline): + return + + def process_slider(self, event): + self.p.layerindex = self.layerslider.GetValue() + z = self.p.get_currentz() + wx.CallAfter(self.SetStatusText, _("Layer %d - Z = %.03f mm") % (self.p.layerindex + 1, z), 0) + self.p.dirty = True + wx.CallAfter(self.p.Refresh) + + def resetview(self, event): + self.p.translate = [0.0, 0.0] + self.p.scale = self.p.basescale + self.p.zoom(0, 0, 1.0) + + def mouse(self, event): + if event.ButtonUp(wx.MOUSE_BTN_LEFT) or event.ButtonUp(wx.MOUSE_BTN_RIGHT): + if self.initpos is not None: + self.initpos = None + elif event.Dragging(): + e = event.GetPositionTuple() + if self.initpos is None: + self.initpos = e + self.basetrans = self.p.translate + self.p.translate = [self.basetrans[0] + (e[0] - self.initpos[0]), + self.basetrans[1] + (e[1] - self.initpos[1])] + self.p.dirty = True + wx.CallAfter(self.p.Refresh) + else: + event.Skip() + + def key(self, event): + # Keycode definitions + kup = [85, 315] # Up keys + kdo = [68, 317] # Down Keys + kzi = [388, 316, 61] # Zoom In Keys + kzo = [390, 314, 45] # Zoom Out Keys + x = event.GetKeyCode() + cx, cy = self.p.translate + if x in kup: + self.p.layerup() + if x in kdo: + self.p.layerdown() + if x in kzi: + self.p.zoom(cx, cy, 1.2) + if x in kzo: + self.p.zoom(cx, cy, 1 / 1.2) + + def zoom(self, event): + z = event.GetWheelRotation() + if event.ShiftDown(): + if z > 0: self.p.layerdown() + elif z < 0: self.p.layerup() + else: + if z > 0: self.p.zoom(event.GetX(), event.GetY(), 1.2) + elif z < 0: self.p.zoom(event.GetX(), event.GetY(), 1 / 1.2) + +class Gviz(wx.Panel): + + # Mark canvas as dirty when setting showall + _showall = 0 + + def _get_showall(self): + return self._showall + + def _set_showall(self, showall): + if showall != self._showall: + self.dirty = True + self._showall = showall + showall = property(_get_showall, _set_showall) + + def __init__(self, parent, size = (200, 200), build_dimensions = [200, 200, 100, 0, 0, 0], grid = (10, 50), extrusion_width = 0.5, bgcolor = "#000000", realparent = None): + wx.Panel.__init__(self, parent, -1) + self.widget = self + size = [max(1.0, x) for x in size] + ratio = size[0] / size[1] + self.SetMinSize((150, 150 / ratio)) + self.parent = realparent if realparent else parent + self.size = size + self.build_dimensions = build_dimensions + self.grid = grid + self.Bind(wx.EVT_PAINT, self.paint) + self.Bind(wx.EVT_SIZE, self.resize) + self.hilight = deque() + self.hilightarcs = deque() + self.hilightqueue = Queue(0) + self.hilightarcsqueue = Queue(0) + self.clear() + self.filament_width = extrusion_width # set it to 0 to disable scaling lines with zoom + self.update_basescale() + self.scale = self.basescale + penwidth = max(1.0, self.filament_width * ((self.scale[0] + self.scale[1]) / 2.0)) + self.translate = [0.0, 0.0] + self.mainpen = wx.Pen(wx.Colour(0, 0, 0), penwidth) + self.arcpen = wx.Pen(wx.Colour(255, 0, 0), penwidth) + self.travelpen = wx.Pen(wx.Colour(10, 80, 80), penwidth) + self.hlpen = wx.Pen(wx.Colour(200, 50, 50), penwidth) + self.fades = [wx.Pen(wx.Colour(250 - 0.6 ** i * 100, 250 - 0.6 ** i * 100, 200 - 0.4 ** i * 50), penwidth) for i in xrange(6)] + self.penslist = [self.mainpen, self.travelpen, self.hlpen] + self.fades + self.bgcolor = wx.Colour() + self.bgcolor.SetFromName(bgcolor) + self.blitmap = wx.EmptyBitmap(self.GetClientSize()[0], self.GetClientSize()[1], -1) + self.paint_overlay = None + + def inject(self): + layer = self.layers.index(self.layerindex) + injector(self.gcode, self.layerindex, layer) + + def editlayer(self): + layer = self.layers.index(self.layerindex) + injector_edit(self.gcode, self.layerindex, layer) + + def clearhilights(self): + self.hilight.clear() + self.hilightarcs.clear() + while not self.hilightqueue.empty(): + self.hilightqueue.get_nowait() + while not self.hilightarcsqueue.empty(): + self.hilightarcsqueue.get_nowait() + + def clear(self): + self.gcode = None + self.lastpos = [0, 0, 0, 0, 0, 0, 0] + self.hilightpos = self.lastpos[:] + self.lines = {} + self.pens = {} + self.arcs = {} + self.arcpens = {} + self.layers = {} + self.layersz = [] + self.clearhilights() + self.layerindex = 0 + self.showall = 0 + self.dirty = True + self.partial = False + self.painted_layers = set() + wx.CallAfter(self.Refresh) + + def get_currentz(self): + z = self.layersz[self.layerindex] + z = 0. if z is None else z + return z + + def layerup(self): + if self.layerindex + 1 < len(self.layers): + self.layerindex += 1 + z = self.get_currentz() + wx.CallAfter(self.parent.SetStatusText, _("Layer %d - Going Up - Z = %.03f mm") % (self.layerindex + 1, z), 0) + self.dirty = True + self.parent.setlayercb(self.layerindex) + wx.CallAfter(self.Refresh) + + def layerdown(self): + if self.layerindex > 0: + self.layerindex -= 1 + z = self.get_currentz() + wx.CallAfter(self.parent.SetStatusText, _("Layer %d - Going Down - Z = %.03f mm") % (self.layerindex + 1, z), 0) + self.dirty = True + self.parent.setlayercb(self.layerindex) + wx.CallAfter(self.Refresh) + + def setlayer(self, layer): + if layer in self.layers: + self.clearhilights() + self.layerindex = self.layers[layer] + self.dirty = True + self.showall = 0 + wx.CallAfter(self.Refresh) + + def update_basescale(self): + self.basescale = 2 * [min(float(self.size[0] - 1) / self.build_dimensions[0], + float(self.size[1] - 1) / self.build_dimensions[1])] + + def resize(self, event): + old_basescale = self.basescale + width, height = self.GetClientSizeTuple() + if width < 1 or height < 1: + return + self.size = (width, height) + self.update_basescale() + zoomratio = float(self.basescale[0]) / old_basescale[0] + wx.CallLater(200, self.zoom, 0, 0, zoomratio) + + def zoom(self, x, y, factor): + if x == -1 and y == -1: + side = min(self.size) + x = y = side / 2 + self.scale = [s * factor for s in self.scale] + + self.translate = [x - (x - self.translate[0]) * factor, + y - (y - self.translate[1]) * factor] + penwidth = max(1.0, self.filament_width * ((self.scale[0] + self.scale[1]) / 2.0)) + for pen in self.penslist: + pen.SetWidth(penwidth) + self.dirty = True + wx.CallAfter(self.Refresh) + + def _line_scaler(self, x): + return (self.scale[0] * x[0], + self.scale[1] * x[1], + self.scale[0] * x[2], + self.scale[1] * x[3],) + + def _arc_scaler(self, x): + return (self.scale[0] * x[0], + self.scale[1] * x[1], + self.scale[0] * x[2], + self.scale[1] * x[3], + self.scale[0] * x[4], + self.scale[1] * x[5],) + + def _drawlines(self, dc, lines, pens): + scaled_lines = map(self._line_scaler, lines) + dc.DrawLineList(scaled_lines, pens) + + def _drawarcs(self, dc, arcs, pens): + scaled_arcs = map(self._arc_scaler, arcs) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + for i in range(len(scaled_arcs)): + dc.SetPen(pens[i] if type(pens) == list else pens) + dc.DrawArc(*scaled_arcs[i]) + + def repaint_everything(self): + width = self.scale[0] * self.build_dimensions[0] + height = self.scale[1] * self.build_dimensions[1] + self.blitmap = wx.EmptyBitmap(width + 1, height + 1, -1) + dc = wx.MemoryDC() + dc.SelectObject(self.blitmap) + dc.SetBackground(wx.Brush((250, 250, 200))) + dc.Clear() + dc.SetPen(wx.Pen(wx.Colour(180, 180, 150))) + for grid_unit in self.grid: + if grid_unit > 0: + for x in xrange(int(self.build_dimensions[0] / grid_unit) + 1): + draw_x = self.scale[0] * x * grid_unit + dc.DrawLine(draw_x, 0, draw_x, height) + for y in xrange(int(self.build_dimensions[1] / grid_unit) + 1): + draw_y = self.scale[1] * (self.build_dimensions[1] - y * grid_unit) + dc.DrawLine(0, draw_y, width, draw_y) + dc.SetPen(wx.Pen(wx.Colour(0, 0, 0))) + + if not self.showall: + # Draw layer gauge + dc.SetBrush(wx.Brush((43, 144, 255))) + dc.DrawRectangle(width - 15, 0, 15, height) + dc.SetBrush(wx.Brush((0, 255, 0))) + if self.layers: + dc.DrawRectangle(width - 14, (1.0 - (1.0 * (self.layerindex + 1)) / len(self.layers)) * height, 13, height - 1) + + if self.showall: + for i in range(len(self.layersz)): + self.painted_layers.add(i) + self._drawlines(dc, self.lines[i], self.pens[i]) + self._drawarcs(dc, self.arcs[i], self.arcpens[i]) + dc.SelectObject(wx.NullBitmap) + return + + if self.layerindex < len(self.layers) and self.layerindex in self.lines: + for layer_i in range(max(0, self.layerindex - 6), self.layerindex): + self._drawlines(dc, self.lines[layer_i], self.fades[self.layerindex - layer_i - 1]) + self._drawarcs(dc, self.arcs[layer_i], self.fades[self.layerindex - layer_i - 1]) + self._drawlines(dc, self.lines[self.layerindex], self.pens[self.layerindex]) + self._drawarcs(dc, self.arcs[self.layerindex], self.arcpens[self.layerindex]) + + self._drawlines(dc, self.hilight, self.hlpen) + self._drawarcs(dc, self.hilightarcs, self.hlpen) + + self.paint_hilights(dc) + + dc.SelectObject(wx.NullBitmap) + + def repaint_partial(self): + if self.showall: + dc = wx.MemoryDC() + dc.SelectObject(self.blitmap) + for i in set(range(len(self.layersz))).difference(self.painted_layers): + self.painted_layers.add(i) + self._drawlines(dc, self.lines[i], self.pens[i]) + self._drawarcs(dc, self.arcs[i], self.arcpens[i]) + dc.SelectObject(wx.NullBitmap) + + def paint_hilights(self, dc = None): + if self.hilightqueue.empty() and self.hilightarcsqueue.empty(): + return + hl = [] + if not dc: + dc = wx.MemoryDC() + dc.SelectObject(self.blitmap) + while not self.hilightqueue.empty(): + hl.append(self.hilightqueue.get_nowait()) + self._drawlines(dc, hl, self.hlpen) + hlarcs = [] + while not self.hilightarcsqueue.empty(): + hlarcs.append(self.hilightarcsqueue.get_nowait()) + self._drawarcs(dc, hlarcs, self.hlpen) + dc.SelectObject(wx.NullBitmap) + + def paint(self, event): + if self.dirty: + self.dirty = False + self.partial = False + self.repaint_everything() + elif self.partial: + self.partial = False + self.repaint_partial() + self.paint_hilights() + dc = wx.PaintDC(self) + dc.SetBackground(wx.Brush(self.bgcolor)) + dc.Clear() + dc.DrawBitmap(self.blitmap, self.translate[0], self.translate[1]) + if self.paint_overlay: + self.paint_overlay(dc) + + def addfile_perlayer(self, gcode, showall = False): + self.clear() + self.gcode = gcode + self.showall = showall + generator = self.add_parsed_gcodes(gcode) + generator_output = generator.next() + while generator_output is not None: + yield generator_output + generator_output = generator.next() + max_layers = len(self.layers) + if hasattr(self.parent, "layerslider"): + self.parent.layerslider.SetRange(0, max_layers - 1) + self.parent.layerslider.SetValue(0) + yield None + + def addfile(self, gcode = None, showall = False): + generator = self.addfile_perlayer(gcode, showall) + while generator.next() is not None: + continue + + def _get_movement(self, start_pos, gline): + """Takes a start position and a gcode, and returns a 3-uple containing + (final position, line, arc), with line and arc being None if not + used""" + target = start_pos[:] + target[5] = 0.0 + target[6] = 0.0 + if gline.current_x is not None: target[0] = gline.current_x + if gline.current_y is not None: target[1] = gline.current_y + if gline.current_z is not None: target[2] = gline.current_z + if gline.e is not None: + if gline.relative_e: + target[3] += gline.e + else: + target[3] = gline.e + if gline.f is not None: target[4] = gline.f + if gline.i is not None: target[5] = gline.i + if gline.j is not None: target[6] = gline.j + + if gline.command in ["G0", "G1"]: + line = [self._x(start_pos[0]), + self._y(start_pos[1]), + self._x(target[0]), + self._y(target[1])] + return target, line, None + elif gline.command in ["G2", "G3"]: + # startpos, endpos, arc center + arc = [self._x(start_pos[0]), self._y(start_pos[1]), + self._x(target[0]), self._y(target[1]), + self._x(start_pos[0] + target[5]), self._y(start_pos[1] + target[6])] + if gline.command == "G2": # clockwise, reverse endpoints + arc[0], arc[1], arc[2], arc[3] = arc[2], arc[3], arc[0], arc[1] + return target, None, arc + + def _y(self, y): + return self.build_dimensions[1] - (y - self.build_dimensions[4]) + + def _x(self, x): + return x - self.build_dimensions[3] + + def add_parsed_gcodes(self, gcode): + start_time = time.time() + + layer_idx = 0 + while layer_idx < len(gcode.all_layers): + layer = gcode.all_layers[layer_idx] + has_move = False + for gline in layer: + if gline.is_move: + has_move = True + break + if not has_move: + yield layer_idx + layer_idx += 1 + continue + viz_layer = len(self.layers) + self.lines[viz_layer] = [] + self.pens[viz_layer] = [] + self.arcs[viz_layer] = [] + self.arcpens[viz_layer] = [] + for gline in layer: + if not gline.is_move: + continue + + target, line, arc = self._get_movement(self.lastpos[:], gline) + + if line is not None: + self.lines[viz_layer].append(line) + self.pens[viz_layer].append(self.mainpen if target[3] != self.lastpos[3] else self.travelpen) + elif arc is not None: + self.arcs[viz_layer].append(arc) + self.arcpens[viz_layer].append(self.arcpen) + + self.lastpos = target + # Transform into a numpy array for memory efficiency + self.lines[viz_layer] = numpy.asarray(self.lines[viz_layer], dtype = numpy.float32) + self.pens[viz_layer] = numpy.asarray(self.pens[viz_layer]) + self.arcs[viz_layer] = numpy.asarray(self.arcs[viz_layer], dtype = numpy.float32) + self.arcpens[viz_layer] = numpy.asarray(self.arcpens[viz_layer]) + # Only add layer to self.layers now to prevent the display of an + # unfinished layer + self.layers[layer_idx] = viz_layer + self.layersz.append(layer.z) + + # Refresh display if more than 0.2s have passed + if time.time() - start_time > 0.2: + start_time = time.time() + self.partial = True + wx.CallAfter(self.Refresh) + + yield layer_idx + layer_idx += 1 + + self.dirty = True + wx.CallAfter(self.Refresh) + yield None + + def addgcodehighlight(self, gline): + if gline.command not in ["G0", "G1", "G2", "G3"]: + return + + target, line, arc = self._get_movement(self.hilightpos[:], gline) + + if line is not None: + self.hilight.append(line) + self.hilightqueue.put_nowait(line) + elif arc is not None: + self.hilightarcs.append(arc) + self.hilightarcsqueue.put_nowait(arc) + + self.hilightpos = target + wx.CallAfter(self.Refresh) + +if __name__ == '__main__': + import sys + app = wx.App(False) + main = GvizWindow(open(sys.argv[1], "rU")) + main.Show() + app.MainLoop() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/injectgcode.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/injectgcode.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,45 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import logging + +from .gui.widgets import MacroEditor + +from .utils import install_locale +install_locale('pronterface') + +def injector(gcode, viz_layer, layer_idx): + cb = lambda toadd: inject(gcode, viz_layer, layer_idx, toadd) + z = gcode.all_layers[layer_idx].z + z = z if z is not None else 0 + MacroEditor(_("Inject G-Code at layer %d (Z = %.03f)") % (viz_layer, z), "", cb, True) + +def injector_edit(gcode, viz_layer, layer_idx): + cb = lambda toadd: rewritelayer(gcode, viz_layer, layer_idx, toadd) + layer = gcode.all_layers[layer_idx] + z = layer.z + z = z if z is not None else 0 + lines = [line.raw for line in layer] + MacroEditor(_("Edit G-Code of layer %d (Z = %.03f)") % (viz_layer, z), lines, cb, True) + +def inject(gcode, viz_layer, layer_idx, toadd): + # TODO: save modified gcode after injection ? + nlines = len(gcode.prepend_to_layer(toadd, layer_idx)) + logging.info(_("Successfully injected %d lines at beginning of layer %d") % (nlines, viz_layer)) + +def rewritelayer(gcode, viz_layer, layer_idx, toadd): + # TODO: save modified gcode after edit ? + nlines = len(gcode.rewrite_layer(toadd, layer_idx)) + logging.info(_("Successfully edited layer %d (which now contains %d lines)") % (viz_layer, nlines)) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/objectplater.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/objectplater.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,300 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +from .utils import install_locale, iconfile +install_locale('plater') + +import logging +import os +import types +import wx + +def patch_method(obj, method, replacement): + orig_handler = getattr(obj, method) + + def wrapped(*a, **kwargs): + kwargs['orig_handler'] = orig_handler + return replacement(*a, **kwargs) + setattr(obj, method, types.MethodType(wrapped, obj)) + +class PlaterPanel(wx.Panel): + def __init__(self, **kwargs): + self.destroy_on_done = False + parent = kwargs.get("parent", None) + super(PlaterPanel, self).__init__(parent = parent) + self.prepare_ui(**kwargs) + + def prepare_ui(self, filenames = [], callback = None, parent = None, build_dimensions = None): + self.filenames = filenames + self.mainsizer = wx.BoxSizer(wx.HORIZONTAL) + panel = self.menupanel = wx.Panel(self, -1) + sizer = self.menusizer = wx.GridBagSizer() + self.l = wx.ListBox(panel) + sizer.Add(self.l, pos = (1, 0), span = (1, 2), flag = wx.EXPAND) + sizer.AddGrowableRow(1, 1) + # Clear button + clearbutton = wx.Button(panel, label = _("Clear")) + clearbutton.Bind(wx.EVT_BUTTON, self.clear) + sizer.Add(clearbutton, pos = (2, 0), span = (1, 2), flag = wx.EXPAND) + # Load button + loadbutton = wx.Button(panel, label = _("Load")) + loadbutton.Bind(wx.EVT_BUTTON, self.load) + sizer.Add(loadbutton, pos = (0, 0), span = (1, 1), flag = wx.EXPAND) + # Snap to Z = 0 button + snapbutton = wx.Button(panel, label = _("Snap to Z = 0")) + snapbutton.Bind(wx.EVT_BUTTON, self.snap) + sizer.Add(snapbutton, pos = (3, 0), span = (1, 1), flag = wx.EXPAND) + # Put at center button + centerbutton = wx.Button(panel, label = _("Put at center")) + centerbutton.Bind(wx.EVT_BUTTON, self.center) + sizer.Add(centerbutton, pos = (3, 1), span = (1, 1), flag = wx.EXPAND) + # Delete button + deletebutton = wx.Button(panel, label = _("Delete")) + deletebutton.Bind(wx.EVT_BUTTON, self.delete) + sizer.Add(deletebutton, pos = (4, 0), span = (1, 1), flag = wx.EXPAND) + # Auto arrange button + autobutton = wx.Button(panel, label = _("Auto arrange")) + autobutton.Bind(wx.EVT_BUTTON, self.autoplate) + sizer.Add(autobutton, pos = (5, 0), span = (1, 2), flag = wx.EXPAND) + # Export button + exportbutton = wx.Button(panel, label = _("Export")) + exportbutton.Bind(wx.EVT_BUTTON, self.export) + sizer.Add(exportbutton, pos = (0, 1), span = (1, 1), flag = wx.EXPAND) + if callback is not None: + donebutton = wx.Button(panel, label = _("Done")) + donebutton.Bind(wx.EVT_BUTTON, lambda e: self.done(e, callback)) + sizer.Add(donebutton, pos = (6, 0), span = (1, 1), flag = wx.EXPAND) + cancelbutton = wx.Button(panel, label = _("Cancel")) + cancelbutton.Bind(wx.EVT_BUTTON, lambda e: self.Destroy()) + sizer.Add(cancelbutton, pos = (6, 1), span = (1, 1), flag = wx.EXPAND) + self.basedir = "." + self.models = {} + panel.SetSizerAndFit(sizer) + self.mainsizer.Add(panel, flag = wx.EXPAND) + self.SetSizer(self.mainsizer) + if build_dimensions: + self.build_dimensions = build_dimensions + else: + self.build_dimensions = [200, 200, 100, 0, 0, 0] + + def set_viewer(self, viewer): + # Patch handle_rotation on the fly + if hasattr(viewer, "handle_rotation"): + def handle_rotation(self, event, orig_handler): + if self.initpos is None: + self.initpos = event.GetPositionTuple() + else: + if event.ShiftDown(): + p1 = self.initpos + p2 = event.GetPositionTuple() + x1, y1, _ = self.mouse_to_3d(p1[0], p1[1]) + x2, y2, _ = self.mouse_to_3d(p2[0], p2[1]) + self.parent.move_shape((x2 - x1, y2 - y1)) + self.initpos = p2 + else: + orig_handler(event) + patch_method(viewer, "handle_rotation", handle_rotation) + # Patch handle_wheel on the fly + if hasattr(viewer, "handle_wheel"): + def handle_wheel(self, event, orig_handler): + if event.ShiftDown(): + delta = event.GetWheelRotation() + angle = 10 + if delta > 0: + self.parent.rotate_shape(angle / 2) + else: + self.parent.rotate_shape(-angle / 2) + else: + orig_handler(event) + patch_method(viewer, "handle_wheel", handle_wheel) + self.s = viewer + self.mainsizer.Add(self.s, 1, wx.EXPAND) + + def move_shape(self, delta): + """moves shape (selected in l, which is list ListBox of shapes) + by an offset specified in tuple delta. + Positive numbers move to (rigt, down)""" + name = self.l.GetSelection() + if name == wx.NOT_FOUND: + return False + + name = self.l.GetString(name) + + model = self.models[name] + model.offsets = [model.offsets[0] + delta[0], + model.offsets[1] + delta[1], + model.offsets[2] + ] + return True + + def rotate_shape(self, angle): + """rotates acive shape + positive angle is clockwise + """ + name = self.l.GetSelection() + if name == wx.NOT_FOUND: + return False + name = self.l.GetString(name) + model = self.models[name] + model.rot += angle + + def autoplate(self, event = None): + logging.info(_("Autoplating")) + separation = 2 + try: + from printrun import packer + p = packer.Packer() + for i in self.models: + width = abs(self.models[i].dims[0] - self.models[i].dims[1]) + height = abs(self.models[i].dims[2] - self.models[i].dims[3]) + p.add_rect(width, height, data = i) + centerx = self.build_dimensions[0] / 2 + self.build_dimensions[3] + centery = self.build_dimensions[1] / 2 + self.build_dimensions[4] + rects = p.pack(padding = separation, + center = packer.Vector2(centerx, centery)) + for rect in rects: + i = rect.data + position = rect.center() + self.models[i].offsets[0] = position.x + self.models[i].offsets[1] = position.y + except ImportError: + bedsize = self.build_dimensions[0:3] + cursor = [0, 0, 0] + newrow = 0 + max = [0, 0] + for i in self.models: + self.models[i].offsets[2] = -1.0 * self.models[i].dims[4] + x = abs(self.models[i].dims[0] - self.models[i].dims[1]) + y = abs(self.models[i].dims[2] - self.models[i].dims[3]) + centre = [x / 2, y / 2] + centreoffset = [self.models[i].dims[0] + centre[0], + self.models[i].dims[2] + centre[1]] + if (cursor[0] + x + separation) >= bedsize[0]: + cursor[0] = 0 + cursor[1] += newrow + separation + newrow = 0 + if (newrow == 0) or (newrow < y): + newrow = y + # To the person who works out why the offsets are applied + # differently here: + # Good job, it confused the hell out of me. + self.models[i].offsets[0] = cursor[0] + centre[0] - centreoffset[0] + self.models[i].offsets[1] = cursor[1] + centre[1] - centreoffset[1] + if (max[0] == 0) or (max[0] < (cursor[0] + x)): + max[0] = cursor[0] + x + if (max[1] == 0) or (max[1] < (cursor[1] + x)): + max[1] = cursor[1] + x + cursor[0] += x + separation + if (cursor[1] + y) >= bedsize[1]: + logging.info(_("Bed full, sorry sir :(")) + self.Refresh() + return + centerx = self.build_dimensions[0] / 2 + self.build_dimensions[3] + centery = self.build_dimensions[1] / 2 + self.build_dimensions[4] + centreoffset = [centerx - max[0] / 2, centery - max[1] / 2] + for i in self.models: + self.models[i].offsets[0] += centreoffset[0] + self.models[i].offsets[1] += centreoffset[1] + self.Refresh() + + def clear(self, event): + result = wx.MessageBox(_('Are you sure you want to clear the grid? All unsaved changes will be lost.'), + _('Clear the grid?'), + wx.YES_NO | wx.ICON_QUESTION) + if result == 2: + self.models = {} + self.l.Clear() + self.Refresh() + + def center(self, event): + i = self.l.GetSelection() + if i != -1: + m = self.models[self.l.GetString(i)] + centerx = self.build_dimensions[0] / 2 + self.build_dimensions[3] + centery = self.build_dimensions[1] / 2 + self.build_dimensions[4] + m.offsets = [centerx, centery, m.offsets[2]] + self.Refresh() + + def snap(self, event): + i = self.l.GetSelection() + if i != -1: + m = self.models[self.l.GetString(i)] + m.offsets[2] = -m.dims[4] + self.Refresh() + + def delete(self, event): + i = self.l.GetSelection() + if i != -1: + del self.models[self.l.GetString(i)] + self.l.Delete(i) + self.l.Select(self.l.GetCount() - 1) + self.Refresh() + + def add_model(self, name, model): + newname = os.path.split(name.lower())[1] + if not isinstance(newname, unicode): + newname = unicode(newname, "utf-8") + c = 1 + while newname in self.models: + newname = os.path.split(name.lower())[1] + newname = newname + "(%d)" % c + c += 1 + self.models[newname] = model + + self.l.Append(newname) + i = self.l.GetSelection() + if i == wx.NOT_FOUND: + self.l.Select(0) + + self.l.Select(self.l.GetCount() - 1) + + def load(self, event): + dlg = wx.FileDialog(self, _("Pick file to load"), self.basedir, style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + dlg.SetWildcard(self.load_wildcard) + if dlg.ShowModal() == wx.ID_OK: + name = dlg.GetPath() + self.load_file(name) + dlg.Destroy() + + def load_file(self, filename): + raise NotImplementedError + + def export(self, event): + dlg = wx.FileDialog(self, _("Pick file to save to"), self.basedir, style = wx.FD_SAVE) + dlg.SetWildcard(self.save_wildcard) + if dlg.ShowModal() == wx.ID_OK: + name = dlg.GetPath() + self.export_to(name) + dlg.Destroy() + + def export_to(self, name): + raise NotImplementedError + +class Plater(wx.Frame): + def __init__(self, **kwargs): + self.destroy_on_done = True + parent = kwargs.get("parent", None) + size = kwargs.get("size", (800, 580)) + if "size" in kwargs: + del kwargs["size"] + wx.Frame.__init__(self, parent, title = _("Plate building tool"), size = size) + self.SetIcon(wx.Icon(iconfile("plater.png"), wx.BITMAP_TYPE_PNG)) + self.prepare_ui(**kwargs) + +def make_plater(panel_class): + name = panel_class.__name__.replace("Panel", "") + return type(name, (Plater, panel_class), {}) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/packer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/packer.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,230 @@ +# Imported from python-rectangle-packer commit 32fce1aaba +# https://github.com/maxretter/python-rectangle-packer +# +# Python Rectangle Packer - Packs rectangles around a central point +# Copyright (C) 2013 Max Retter +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import math + +import Polygon +import Polygon.Utils + + +class Vector2(object): + """Simple 2d vector / point class.""" + + def __init__(self, x=0, y=0): + self.x = float(x) + self.y = float(y) + + def __eq__(self, other): + return self.x == other.x and self.y == other.y + + def add(self, other): + return Vector2(self.x + other.x, self.y + other.y) + + def sub(self, other): + return Vector2(self.x - other.x, self.y - other.y) + + def scale(self, factor): + return Vector2(self.x * factor, self.y * factor) + + def magnitude(self): + return math.sqrt(self.dot_product(self)) + + def unit(self): + """Build unit vector.""" + return self.scale(1 / self.magnitude()) + + def dot_product(self, other): + return self.x * other.x + self.y * other.y + + def distance(self, other): + """Distance forumla for other point.""" + return math.sqrt( + (other.x - self.x) ** 2 + + (other.y - self.y) ** 2 + ) + + +class Rect(object): + """Simple rectangle object.""" + def __init__(self, width, height, data={}): + self.width = width + self.height = height + self.data = data + + # upper left + self.position = Vector2() + + def half(self): + """Half width and height.""" + return Vector2( + self.width / 2, + self.height / 2 + ) + + def expand(self, width, height): + """Builds a new rectangle based on this one with given offsets.""" + expanded = Rect(self.width + width, self.height + height) + expanded.set_center(self.center()) + + return expanded + + def point_list(self): + top = self.position.y + right = self.position.x + self.width + bottom = self.position.y + self.height + left = self.position.x + + return PointList([ + (left, top), + (right, top), + (right, bottom), + (left, bottom), + ]) + + def center(self): + """Center of rect calculated from position and dimensions.""" + return self.position.add(self.half()) + + def set_center(self, center): + """Set the position based on a new center point.""" + self.position = center.sub(self.half()) + + def area(self): + """Area: length * width.""" + return self.width * self.height + + +class PointList(object): + """Methods for transforming a list of points.""" + def __init__(self, points=[]): + self.points = points + self._polygon = None + + def polygon(self): + """Builds a polygon from the set of points.""" + if not self._polygon: + self._polygon = Polygon.Polygon(self.points) + + return self._polygon + + def segments(self): + """Returns a list of LineSegment objects.""" + segs = [] + for i, point in enumerate(self.points[1:]): + index = i + 1 + + segs.append(LineSegment( + Vector2(self.points[index - 1][0], self.points[index - 1][1]), + Vector2(self.points[index][0], self.points[index][1]) + )) + + segs.append(LineSegment( + Vector2(self.points[-1][0], self.points[-1][1]), + Vector2(self.points[0][0], self.points[0][1]), + )) + + return segs + + +class LineSegment(object): + def __init__(self, start, end): + self.start = start + self.end = end + + def length(self): + """Length of segment vector.""" + return self.end.sub(self.start).magnitude() + + def closest_point_to_point(self, point): + """Point along segment that is closest to given point.""" + segment_vector = self.end.sub(self.start) + point_vector = point.sub(self.start) + + seg_mag = segment_vector.magnitude() + + # project point_vector on segment_vector + projection = segment_vector.dot_product(point_vector) + + # scalar value used to interpolate new point along segment_vector + scalar = projection / seg_mag ** 2 + + # clamp on [0,1] + scalar = 1.0 if scalar > 1.0 else scalar + scalar = 0.0 if scalar < 0.0 else scalar + + # interpolate scalar along segment and add start point back in + return self.start.add(segment_vector.unit().scale(scalar * seg_mag)) + + def closest_distance_to_point(self, point): + """Helper method too automatically return distance.""" + closest_point = self.closest_point_to_point(point) + return closest_point.distance(point) + + +class Packer(object): + def __init__(self): + self._rects = [] + + def add_rect(self, width, height, data={}): + self._rects.append(Rect(width, height, data)) + + def pack(self, padding=0, center=Vector2()): + # init everything + placed_rects = [] + sorted_rects = sorted(self._rects, key=lambda rect: -rect.area()) + # double padding due to halfing later on + padding *= 2 + + for rect in sorted_rects: + + if not placed_rects: + # first rect, right on target. + rect.set_center(center) + + else: + # Expand each rectangle based on new rect size and padding + # get a list of points + # build a polygon + point_lists = [ + pr.expand(rect.width + padding, rect.height + padding).point_list().polygon() + for pr in placed_rects + ] + + # take the union of all the polygons (relies on + operator override) + # the [0] at the end returns the first "contour", which is the only one we need + bounding_points = PointList(sum( + point_lists[1:], + point_lists[0] + )[0]) + + # find the closest segment + closest_segments = sorted( + bounding_points.segments(), + key=lambda segment: segment.closest_distance_to_point(center) + ) + + # get the closest point + place_point = closest_segments[0].closest_point_to_point(center) + + # set the rect position + rect.set_center(place_point) + + placed_rects.append(rect) + + return placed_rects diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/power/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/power/__init__.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,142 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import platform +import logging +import os + +if platform.system() == "Darwin": + from .osx import inhibit_sleep_osx, deinhibit_sleep_osx + inhibit_sleep = inhibit_sleep_osx + deinhibit_sleep = deinhibit_sleep_osx +elif platform.system() == "Windows": + import ctypes + ES_CONTINUOUS = 0x80000000 + ES_SYSTEM_REQUIRED = 0x00000001 + + def inhibit_sleep(reason): + mode = ES_CONTINUOUS | ES_SYSTEM_REQUIRED + ctypes.windll.kernel32.SetThreadExecutionState(ctypes.c_int(mode)) + + def deinhibit_sleep(): + ctypes.windll.kernel32.SetThreadExecutionState(ctypes.c_int(ES_CONTINUOUS)) +else: + try: + import dbus + inhibit_sleep_handler = None + inhibit_sleep_token = None + bus = dbus.SessionBus() + try: + # GNOME uses the right object path, try it first + service_name = "org.freedesktop.ScreenSaver" + proxy = bus.get_object(service_name, + "/org/freedesktop/ScreenSaver") + inhibit_sleep_handler = dbus.Interface(proxy, service_name) + # Do a test run + token = inhibit_sleep_handler.Inhibit("printrun", "test") + inhibit_sleep_handler.UnInhibit(token) + except dbus.DBusException: + # KDE uses /ScreenSaver object path, let's try it as well + proxy = bus.get_object(service_name, + "/ScreenSaver") + inhibit_sleep_handler = dbus.Interface(proxy, service_name) + token = inhibit_sleep_handler.Inhibit("printrun", "test") + inhibit_sleep_handler.UnInhibit(token) + + def inhibit_sleep(reason): + global inhibit_sleep_handler, inhibit_sleep_token + inhibit_sleep_token = inhibit_sleep_handler.Inhibit("printrun", reason) + + def deinhibit_sleep(): + global inhibit_sleep_handler, inhibit_sleep_token + if inhibit_sleep_handler is None or inhibit_sleep_token is None: + return + inhibit_sleep_handler.UnInhibit(inhibit_sleep_token) + inhibit_sleep_token = None + except Exception, e: + logging.warning("Could not setup DBus for sleep inhibition: %s" % e) + + def inhibit_sleep(reason): + return + + def deinhibit_sleep(): + return + +try: + import psutil + + def get_nice(nice, p = None): + if not p: p = psutil.Process(os.getpid()) + if callable(p.nice): + return p.nice() + else: + return p.nice + + def set_nice(nice, p = None): + if not p: p = psutil.Process(os.getpid()) + if callable(p.nice): + p.nice(nice) + else: + p.nice = nice + + if platform.system() != "Windows": + import resource + if hasattr(psutil, "RLIMIT_NICE"): + nice_limit, _ = resource.getrlimit(psutil.RLIMIT_NICE) + high_priority_nice = 20 - nice_limit + else: + high_priority_nice = 0 + # RLIMIT_NICE is not available (probably OSX), let's probe + # Try setting niceness to -20 .. -1 + p = psutil.Process(os.getpid()) + orig_nice = get_nice(p) + for i in range(-20, 0): + try: + set_nice(i, p) + high_priority_nice = i + break + except psutil.AccessDenied, e: + pass + set_nice(orig_nice, p) + + def set_priority(): + if platform.system() == "Windows": + set_nice(psutil.HIGH_PRIORITY_CLASS) + else: + if high_priority_nice < 0: + set_nice(high_priority_nice) + + def reset_priority(): + if platform.system() == "Windows": + set_nice(psutil.NORMAL_PRIORITY_CLASS) + else: + if high_priority_nice < 0: + set_nice(0) + + def powerset_print_start(reason): + set_priority() + inhibit_sleep(reason) + + def powerset_print_stop(): + reset_priority() + deinhibit_sleep() +except ImportError, e: + logging.warning("psutil unavailable, could not import power utils:" + str(e)) + + def powerset_print_start(reason): + pass + + def powerset_print_stop(): + pass diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/power/osx.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/power/osx.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,82 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . +# +# Imported from http://www.benden.us/journal/2014/OS-X-Power-Management-No-Sleep-Howto/ +# Copyright (c) Joseph Benden 2014 + +import ctypes +import CoreFoundation +import objc + +def SetUpIOFramework(): + # load the IOKit library + framework = ctypes.cdll.LoadLibrary( + '/System/Library/Frameworks/IOKit.framework/IOKit') + + # declare parameters as described in IOPMLib.h + framework.IOPMAssertionCreateWithName.argtypes = [ + ctypes.c_void_p, # CFStringRef + ctypes.c_uint32, # IOPMAssertionLevel + ctypes.c_void_p, # CFStringRef + ctypes.POINTER(ctypes.c_uint32)] # IOPMAssertionID + framework.IOPMAssertionRelease.argtypes = [ + ctypes.c_uint32] # IOPMAssertionID + return framework + +def StringToCFString(string): + # we'll need to convert our strings before use + try: + encoding = CoreFoundation.kCFStringEncodingASCII + except AttributeError: + encoding = 0x600 + cfstring = CoreFoundation.CFStringCreateWithCString(None, string, encoding) + return objc.pyobjc_id(cfstring.nsstring()) + +def AssertionCreateWithName(framework, a_type, + a_level, a_reason): + # this method will create an assertion using the IOKit library + # several parameters + a_id = ctypes.c_uint32(0) + a_type = StringToCFString(a_type) + a_reason = StringToCFString(a_reason) + a_error = framework.IOPMAssertionCreateWithName( + a_type, a_level, a_reason, ctypes.byref(a_id)) + + # we get back a 0 or stderr, along with a unique c_uint + # representing the assertion ID so we can release it later + return a_error, a_id + +def AssertionRelease(framework, assertion_id): + # releasing the assertion is easy, and also returns a 0 on + # success, or stderr otherwise + return framework.IOPMAssertionRelease(assertion_id) + +def inhibit_sleep_osx(reason): + no_idle = "NoIdleSleepAssertion" + + # Initialize IOKit framework + if inhibit_sleep_osx.framework is None: + inhibit_sleep_osx.framework = SetUpIOFramework() + framework = inhibit_sleep_osx.framework + + # Start inhibition + ret, a_id = AssertionCreateWithName(framework, no_idle, 255, reason) + inhibit_sleep_osx.assertion_id = a_id + return ret +inhibit_sleep_osx.framework = None + +def deinhibit_sleep_osx(): + return AssertionRelease(inhibit_sleep_osx.framework, + inhibit_sleep_osx.assertion_id) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/printcore.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/printcore.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,629 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +__version__ = "2015.03.10" + +from serialWrapper import Serial, SerialException, PARITY_ODD, PARITY_NONE +from select import error as SelectError +import threading +from Queue import Queue, Empty as QueueEmpty +import time +import platform +import os +import sys +stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr +reload(sys).setdefaultencoding('utf8') +sys.stdin, sys.stdout, sys.stderr = stdin, stdout, stderr +import logging +import traceback +import errno +import socket +import re +from functools import wraps +from collections import deque +from printrun import gcoder +from .utils import install_locale, decode_utf8 +install_locale('pronterface') + +def locked(f): + @wraps(f) + def inner(*args, **kw): + with inner.lock: + return f(*args, **kw) + inner.lock = threading.Lock() + return inner + +def control_ttyhup(port, disable_hup): + """Controls the HUPCL""" + if platform.system() == "Linux": + if disable_hup: + os.system("stty -F %s -hup" % port) + else: + os.system("stty -F %s hup" % port) + +def enable_hup(port): + control_ttyhup(port, False) + +def disable_hup(port): + control_ttyhup(port, True) + +class printcore(): + def __init__(self, port = None, baud = None, dtr=None): + """Initializes a printcore instance. Pass the port and baud rate to + connect immediately""" + self.baud = None + self.dtr = None + self.port = None + self.analyzer = gcoder.GCode() + # Serial instance connected to the printer, should be None when + # disconnected + self.printer = None + # clear to send, enabled after responses + # FIXME: should probably be changed to a sliding window approach + self.clear = 0 + # The printer has responded to the initial command and is active + self.online = False + # is a print currently running, true if printing, false if paused + self.printing = False + self.mainqueue = None + self.priqueue = Queue(0) + self.queueindex = 0 + self.lineno = 0 + self.resendfrom = -1 + self.paused = False + self.sentlines = {} + self.log = deque(maxlen = 10000) + self.sent = [] + self.writefailures = 0 + self.tempcb = None # impl (wholeline) + self.recvcb = None # impl (wholeline) + self.sendcb = None # impl (wholeline) + self.preprintsendcb = None # impl (wholeline) + self.printsendcb = None # impl (wholeline) + self.layerchangecb = None # impl (wholeline) + self.errorcb = None # impl (wholeline) + self.startcb = None # impl () + self.endcb = None # impl () + self.onlinecb = None # impl () + self.loud = False # emit sent and received lines to terminal + self.tcp_streaming_mode = False + self.greetings = ['start', 'Grbl '] + self.wait = 0 # default wait period for send(), send_now() + self.read_thread = None + self.stop_read_thread = False + self.send_thread = None + self.stop_send_thread = False + self.print_thread = None + if port is not None and baud is not None: + self.connect(port, baud) + self.xy_feedrate = None + self.z_feedrate = None + + def logError(self, error): + if self.errorcb: + try: self.errorcb(error) + except: logging.error(traceback.format_exc()) + else: + logging.error(error) + + @locked + def disconnect(self): + """Disconnects from printer and pauses the print + """ + if self.printer: + if self.read_thread: + self.stop_read_thread = True + if threading.current_thread() != self.read_thread: + self.read_thread.join() + self.read_thread = None + if self.print_thread: + self.printing = False + self.print_thread.join() + self._stop_sender() + try: + self.printer.close() + except socket.error: + pass + except OSError: + pass + self.printer = None + self.online = False + self.printing = False + + @locked + def connect(self, port = None, baud = None, dtr=None): + """Set port and baudrate if given, then connect to printer + """ + if self.printer: + self.disconnect() + if port is not None: + self.port = port + if baud is not None: + self.baud = baud + if dtr is not None: + self.dtr = dtr + if self.port is not None and self.baud is not None: + # Connect to socket if "port" is an IP, device if not + host_regexp = re.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$") + is_serial = True + if ":" in port: + bits = port.split(":") + if len(bits) == 2: + hostname = bits[0] + try: + port = int(bits[1]) + if host_regexp.match(hostname) and 1 <= port <= 65535: + is_serial = False + except: + pass + self.writefailures = 0 + if not is_serial: + self.printer_tcp = socket.socket(socket.AF_INET, + socket.SOCK_STREAM) + self.printer_tcp.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + self.timeout = 0.25 + self.printer_tcp.settimeout(1.0) + try: + self.printer_tcp.connect((hostname, port)) + self.printer_tcp.settimeout(self.timeout) + self.printer = self.printer_tcp.makefile() + except socket.error as e: + if(e.strerror is None): e.strerror="" + self.logError(_("Could not connect to %s:%s:") % (hostname, port) + + "\n" + _("Socket error %s:") % e.errno + + "\n" + e.strerror) + self.printer = None + self.printer_tcp = None + return + else: + disable_hup(self.port) + self.printer_tcp = None + try: + self.printer = Serial(port = self.port, + baudrate = self.baud, + timeout = 0.25, + parity = PARITY_ODD) + self.printer.close() + self.printer.parity = PARITY_NONE + try: #this appears not to work on many platforms, so we're going to call it but not care if it fails + self.printer.setDTR(dtr); + except: + #self.logError(_("Could not set DTR on this platform")) #not sure whether to output an error message + pass + self.printer.open() + except SerialException as e: + self.logError(_("Could not connect to %s at baudrate %s:") % (self.port, self.baud) + + "\n" + _("Serial error: %s") % e) + self.printer = None + return + except IOError as e: + self.logError(_("Could not connect to %s at baudrate %s:") % (self.port, self.baud) + + "\n" + _("IO error: %s") % e) + self.printer = None + return + self.stop_read_thread = False + self.read_thread = threading.Thread(target = self._listen) + self.read_thread.start() + self._start_sender() + + def reset(self): + """Reset the printer + """ + if self.printer and not self.printer_tcp: + self.printer.setDTR(1) + time.sleep(0.2) + self.printer.setDTR(0) + + def _readline(self): + try: + try: + line = self.printer.readline() + if self.printer_tcp and not line: + raise OSError(-1, "Read EOF from socket") + except socket.timeout: + return "" + + if len(line) > 1: + self.log.append(line) + if self.recvcb: + try: self.recvcb(line) + except: self.logError(traceback.format_exc()) + if self.loud: logging.info("RECV: %s" % line.rstrip()) + return line + except SelectError as e: + if 'Bad file descriptor' in e.args[1]: + self.logError(_(u"Can't read from printer (disconnected?) (SelectError {0}): {1}").format(e.errno, decode_utf8(e.strerror))) + return None + else: + self.logError(_(u"SelectError ({0}): {1}").format(e.errno, decode_utf8(e.strerror))) + raise + except SerialException as e: + self.logError(_(u"Can't read from printer (disconnected?) (SerialException): {0}").format(decode_utf8(str(e)))) + return None + except socket.error as e: + self.logError(_(u"Can't read from printer (disconnected?) (Socket error {0}): {1}").format(e.errno, decode_utf8(e.strerror))) + return None + except OSError as e: + if e.errno == errno.EAGAIN: # Not a real error, no data was available + return "" + self.logError(_(u"Can't read from printer (disconnected?) (OS Error {0}): {1}").format(e.errno, e.strerror)) + return None + + def _listen_can_continue(self): + if self.printer_tcp: + return not self.stop_read_thread and self.printer + return (not self.stop_read_thread + and self.printer + and self.printer.isOpen()) + + def _listen_until_online(self): + while not self.online and self._listen_can_continue(): + self._send("M105") + if self.writefailures >= 4: + logging.error(_("Aborting connection attempt after 4 failed writes.")) + return + empty_lines = 0 + while self._listen_can_continue(): + line = self._readline() + if line is None: break # connection problem + # workaround cases where M105 was sent before printer Serial + # was online an empty line means read timeout was reached, + # meaning no data was received thus we count those empty lines, + # and once we have seen 15 in a row, we just break and send a + # new M105 + # 15 was chosen based on the fact that it gives enough time for + # Gen7 bootloader to time out, and that the non received M105 + # issues should be quite rare so we can wait for a long time + # before resending + if not line: + empty_lines += 1 + if empty_lines == 15: break + else: empty_lines = 0 + if line.startswith(tuple(self.greetings)) \ + or line.startswith('ok') or "T:" in line: + self.online = True + if self.onlinecb: + try: self.onlinecb() + except: self.logError(traceback.format_exc()) + return + + def _listen(self): + """This function acts on messages from the firmware + """ + self.clear = True + if not self.printing: + self._listen_until_online() + while self._listen_can_continue(): + line = self._readline() + if line is None: + break + if line.startswith('DEBUG_'): + continue + if line.startswith(tuple(self.greetings)) or line.startswith('ok'): + self.clear = True + if line.startswith('ok') and "T:" in line and self.tempcb: + # callback for temp, status, whatever + try: self.tempcb(line) + except: self.logError(traceback.format_exc()) + elif line.startswith('Error'): + self.logError(line) + # Teststrings for resend parsing # Firmware exp. result + # line="rs N2 Expected checksum 67" # Teacup 2 + if line.lower().startswith("resend") or line.startswith("rs"): + for haystack in ["N:", "N", ":"]: + line = line.replace(haystack, " ") + linewords = line.split() + while len(linewords) != 0: + try: + toresend = int(linewords.pop(0)) + self.resendfrom = toresend + break + except: + pass + self.clear = True + self.clear = True + + def _start_sender(self): + self.stop_send_thread = False + self.send_thread = threading.Thread(target = self._sender) + self.send_thread.start() + + def _stop_sender(self): + if self.send_thread: + self.stop_send_thread = True + self.send_thread.join() + self.send_thread = None + + def _sender(self): + while not self.stop_send_thread: + try: + command = self.priqueue.get(True, 0.1) + except QueueEmpty: + continue + while self.printer and self.printing and not self.clear: + time.sleep(0.001) + self._send(command) + while self.printer and self.printing and not self.clear: + time.sleep(0.001) + + def _checksum(self, command): + return reduce(lambda x, y: x ^ y, map(ord, command)) + + def startprint(self, gcode, startindex = 0): + """Start a print, gcode is an array of gcode commands. + returns True on success, False if already printing. + The print queue will be replaced with the contents of the data array, + the next line will be set to 0 and the firmware notified. Printing + will then start in a parallel thread. + """ + if self.printing or not self.online or not self.printer: + return False + self.queueindex = startindex + self.mainqueue = gcode + self.printing = True + self.lineno = 0 + self.resendfrom = -1 + self._send("M110", -1, True) + if not gcode or not gcode.lines: + return True + self.clear = False + resuming = (startindex != 0) + self.print_thread = threading.Thread(target = self._print, + kwargs = {"resuming": resuming}) + self.print_thread.start() + return True + + def cancelprint(self): + self.pause() + self.paused = False + self.mainqueue = None + self.clear = True + + # run a simple script if it exists, no multithreading + def runSmallScript(self, filename): + if filename is None: return + f = None + try: + with open(filename) as f: + for i in f: + l = i.replace("\n", "") + l = l[:l.find(";")] # remove comments + self.send_now(l) + except: + pass + + def pause(self): + """Pauses the print, saving the current position. + """ + if not self.printing: return False + self.paused = True + self.printing = False + + # try joining the print thread: enclose it in try/except because we + # might be calling it from the thread itself + try: + self.print_thread.join() + except RuntimeError, e: + if e.message == "cannot join current thread": + pass + else: + self.logError(traceback.format_exc()) + except: + self.logError(traceback.format_exc()) + + self.print_thread = None + + # saves the status + self.pauseX = self.analyzer.abs_x + self.pauseY = self.analyzer.abs_y + self.pauseZ = self.analyzer.abs_z + self.pauseE = self.analyzer.abs_e + self.pauseF = self.analyzer.current_f + self.pauseRelative = self.analyzer.relative + + def resume(self): + """Resumes a paused print. + """ + if not self.paused: return False + if self.paused: + # restores the status + self.send_now("G90") # go to absolute coordinates + + xyFeedString = "" + zFeedString = "" + if self.xy_feedrate is not None: + xyFeedString = " F" + str(self.xy_feedrate) + if self.z_feedrate is not None: + zFeedString = " F" + str(self.z_feedrate) + + self.send_now("G1 X%s Y%s%s" % (self.pauseX, self.pauseY, + xyFeedString)) + self.send_now("G1 Z" + str(self.pauseZ) + zFeedString) + self.send_now("G92 E" + str(self.pauseE)) + + # go back to relative if needed + if self.pauseRelative: self.send_now("G91") + # reset old feed rate + self.send_now("G1 F" + str(self.pauseF)) + + self.paused = False + self.printing = True + self.print_thread = threading.Thread(target = self._print, + kwargs = {"resuming": True}) + self.print_thread.start() + + def send(self, command, wait = 0): + """Adds a command to the checksummed main command queue if printing, or + sends the command immediately if not printing""" + + if self.online: + if self.printing: + self.mainqueue.append(command) + else: + self.priqueue.put_nowait(command) + else: + self.logError(_("Not connected to printer.")) + + def send_now(self, command, wait = 0): + """Sends a command to the printer ahead of the command queue, without a + checksum""" + if self.online: + self.priqueue.put_nowait(command) + else: + self.logError(_("Not connected to printer.")) + + def _print(self, resuming = False): + self._stop_sender() + try: + if self.startcb: + # callback for printing started + try: self.startcb(resuming) + except: + self.logError(_("Print start callback failed with:") + + "\n" + traceback.format_exc()) + while self.printing and self.printer and self.online: + self._sendnext() + self.sentlines = {} + self.log.clear() + self.sent = [] + if self.endcb: + # callback for printing done + try: self.endcb() + except: + self.logError(_("Print end callback failed with:") + + "\n" + traceback.format_exc()) + except: + self.logError(_("Print thread died due to the following error:") + + "\n" + traceback.format_exc()) + finally: + self.print_thread = None + self._start_sender() + + def process_host_command(self, command): + """only ;@pause command is implemented as a host command in printcore, but hosts are free to reimplement this method""" + command = command.lstrip() + if command.startswith(";@pause"): + self.pause() + + def _sendnext(self): + if not self.printer: + return + while self.printer and self.printing and not self.clear: + time.sleep(0.001) + # Only wait for oks when using serial connections or when not using tcp + # in streaming mode + if not self.printer_tcp or not self.tcp_streaming_mode: + self.clear = False + if not (self.printing and self.printer and self.online): + self.clear = True + return + if self.resendfrom < self.lineno and self.resendfrom > -1: + self._send(self.sentlines[self.resendfrom], self.resendfrom, False) + self.resendfrom += 1 + return + self.resendfrom = -1 + if not self.priqueue.empty(): + self._send(self.priqueue.get_nowait()) + self.priqueue.task_done() + return + if self.printing and self.queueindex < len(self.mainqueue): + (layer, line) = self.mainqueue.idxs(self.queueindex) + gline = self.mainqueue.all_layers[layer][line] + if self.layerchangecb and self.queueindex > 0: + (prev_layer, prev_line) = self.mainqueue.idxs(self.queueindex - 1) + if prev_layer != layer: + try: self.layerchangecb(layer) + except: self.logError(traceback.format_exc()) + if self.preprintsendcb: + if self.queueindex + 1 < len(self.mainqueue): + (next_layer, next_line) = self.mainqueue.idxs(self.queueindex + 1) + next_gline = self.mainqueue.all_layers[next_layer][next_line] + else: + next_gline = None + gline = self.preprintsendcb(gline, next_gline) + if gline is None: + self.queueindex += 1 + self.clear = True + return + tline = gline.raw + if tline.lstrip().startswith(";@"): # check for host command + self.process_host_command(tline) + self.queueindex += 1 + self.clear = True + return + + # Strip comments + tline = gcoder.gcode_strip_comment_exp.sub("", tline).strip() + if tline: + self._send(tline, self.lineno, True) + self.lineno += 1 + if self.printsendcb: + try: self.printsendcb(gline) + except: self.logError(traceback.format_exc()) + else: + self.clear = True + self.queueindex += 1 + else: + self.printing = False + self.clear = True + if not self.paused: + self.queueindex = 0 + self.lineno = 0 + self._send("M110", -1, True) + + def _send(self, command, lineno = 0, calcchecksum = False): + # Only add checksums if over serial (tcp does the flow control itself) + if calcchecksum and not self.printer_tcp: + prefix = "N" + str(lineno) + " " + command + command = prefix + "*" + str(self._checksum(prefix)) + if "M110" not in command: + self.sentlines[lineno] = command + if self.printer: + self.sent.append(command) + # run the command through the analyzer + gline = None + try: + gline = self.analyzer.append(command, store = False) + except: + logging.warning(_("Could not analyze command %s:") % command + + "\n" + traceback.format_exc()) + if self.loud: + logging.info("SENT: %s" % command) + if self.sendcb: + try: self.sendcb(command, gline) + except: self.logError(traceback.format_exc()) + try: + self.printer.write(str(command + "\n")) + if self.printer_tcp: + try: + self.printer.flush() + except socket.timeout: + pass + self.writefailures = 0 + except socket.error as e: + if e.errno is None: + self.logError(_(u"Can't write to printer (disconnected ?):") + + "\n" + traceback.format_exc()) + else: + self.logError(_(u"Can't write to printer (disconnected?) (Socket error {0}): {1}").format(e.errno, decode_utf8(e.strerror))) + self.writefailures += 1 + except SerialException as e: + self.logError(_(u"Can't write to printer (disconnected?) (SerialException): {0}").format(decode_utf8(str(e)))) + self.writefailures += 1 + except RuntimeError as e: + self.logError(_(u"Socket connection broken, disconnected. ({0}): {1}").format(e.errno, decode_utf8(e.strerror))) + self.writefailures += 1 diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/projectlayer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/projectlayer.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,817 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import xml.etree.ElementTree +import wx +import wx.lib.agw.floatspin as floatspin +import os +import time +import zipfile +import tempfile +import shutil +from cairosvg.surface import PNGSurface +import cStringIO +import imghdr +import copy +import re +from collections import OrderedDict +import itertools +import math + +class DisplayFrame(wx.Frame): + def __init__(self, parent, title, res = (1024, 768), printer = None, scale = 1.0, offset = (0, 0)): + wx.Frame.__init__(self, parent = parent, title = title, size = res) + self.printer = printer + self.control_frame = parent + self.pic = wx.StaticBitmap(self) + self.bitmap = wx.EmptyBitmap(*res) + self.bbitmap = wx.EmptyBitmap(*res) + self.slicer = 'bitmap' + self.dpi = 96 + dc = wx.MemoryDC() + dc.SelectObject(self.bbitmap) + dc.SetBackground(wx.Brush("black")) + dc.Clear() + dc.SelectObject(wx.NullBitmap) + + self.SetBackgroundColour("black") + self.pic.Hide() + self.SetDoubleBuffered(True) + self.SetPosition((self.control_frame.GetSize().x, 0)) + self.Show() + + self.scale = scale + self.index = 0 + self.size = res + self.offset = offset + self.running = False + self.layer_red = False + + def clear_layer(self): + try: + dc = wx.MemoryDC() + dc.SelectObject(self.bitmap) + dc.SetBackground(wx.Brush("black")) + dc.Clear() + self.pic.SetBitmap(self.bitmap) + self.pic.Show() + self.Refresh() + except: + raise + pass + + def resize(self, res = (1024, 768)): + self.bitmap = wx.EmptyBitmap(*res) + self.bbitmap = wx.EmptyBitmap(*res) + dc = wx.MemoryDC() + dc.SelectObject(self.bbitmap) + dc.SetBackground(wx.Brush("black")) + dc.Clear() + dc.SelectObject(wx.NullBitmap) + + def draw_layer(self, image): + try: + dc = wx.MemoryDC() + dc.SelectObject(self.bitmap) + dc.SetBackground(wx.Brush("black")) + dc.Clear() + + if self.slicer == 'Slic3r' or self.slicer == 'Skeinforge': + + if self.scale != 1.0: + layercopy = copy.deepcopy(image) + height = float(layercopy.get('height').replace('m', '')) + width = float(layercopy.get('width').replace('m', '')) + + layercopy.set('height', str(height * self.scale) + 'mm') + layercopy.set('width', str(width * self.scale) + 'mm') + layercopy.set('viewBox', '0 0 ' + str(width * self.scale) + ' ' + str(height * self.scale)) + + g = layercopy.find("{http://www.w3.org/2000/svg}g") + g.set('transform', 'scale(' + str(self.scale) + ')') + stream = cStringIO.StringIO(PNGSurface.convert(dpi = self.dpi, bytestring = xml.etree.ElementTree.tostring(layercopy))) + else: + stream = cStringIO.StringIO(PNGSurface.convert(dpi = self.dpi, bytestring = xml.etree.ElementTree.tostring(image))) + + pngImage = wx.ImageFromStream(stream) + + # print "w:", pngImage.Width, ", dpi:", self.dpi, ", w (mm): ",(pngImage.Width / self.dpi) * 25.4 + + if self.layer_red: + pngImage = pngImage.AdjustChannels(1, 0, 0, 1) + + dc.DrawBitmap(wx.BitmapFromImage(pngImage), self.offset[0], self.offset[1], True) + + elif self.slicer == 'bitmap': + if isinstance(image, str): + image = wx.Image(image) + if self.layer_red: + image = image.AdjustChannels(1, 0, 0, 1) + dc.DrawBitmap(wx.BitmapFromImage(image.Scale(image.Width * self.scale, image.Height * self.scale)), self.offset[0], -self.offset[1], True) + else: + raise Exception(self.slicer + " is an unknown method.") + + self.pic.SetBitmap(self.bitmap) + self.pic.Show() + self.Refresh() + + except: + raise + pass + + def show_img_delay(self, image): + print "Showing", str(time.clock()) + self.control_frame.set_current_layer(self.index) + self.draw_layer(image) + wx.FutureCall(1000 * self.interval, self.hide_pic_and_rise) + + def rise(self): + if (self.direction == "Top Down"): + print "Lowering", str(time.clock()) + else: + print "Rising", str(time.clock()) + + if self.printer is not None and self.printer.online: + self.printer.send_now("G91") + + if (self.prelift_gcode): + for line in self.prelift_gcode.split('\n'): + if line: + self.printer.send_now(line) + + if (self.direction == "Top Down"): + self.printer.send_now("G1 Z-%f F%g" % (self.overshoot, self.z_axis_rate,)) + self.printer.send_now("G1 Z%f F%g" % (self.overshoot - self.thickness, self.z_axis_rate,)) + else: # self.direction == "Bottom Up" + self.printer.send_now("G1 Z%f F%g" % (self.overshoot, self.z_axis_rate,)) + self.printer.send_now("G1 Z-%f F%g" % (self.overshoot - self.thickness, self.z_axis_rate,)) + + if (self.postlift_gcode): + for line in self.postlift_gcode.split('\n'): + if line: + self.printer.send_now(line) + + self.printer.send_now("G90") + else: + time.sleep(self.pause) + + wx.FutureCall(1000 * self.pause, self.next_img) + + def hide_pic(self): + print "Hiding", str(time.clock()) + self.pic.Hide() + + def hide_pic_and_rise(self): + wx.CallAfter(self.hide_pic) + wx.FutureCall(500, self.rise) + + def next_img(self): + if not self.running: + return + if self.index < len(self.layers): + print self.index + wx.CallAfter(self.show_img_delay, self.layers[self.index]) + self.index += 1 + else: + print "end" + wx.CallAfter(self.pic.Hide) + wx.CallAfter(self.Refresh) + + def present(self, + layers, + interval = 0.5, + pause = 0.2, + overshoot = 0.0, + z_axis_rate = 200, + prelift_gcode = "", + postlift_gcode = "", + direction = "Top Down", + thickness = 0.4, + scale = 1, + size = (1024, 768), + offset = (0, 0), + layer_red = False): + wx.CallAfter(self.pic.Hide) + wx.CallAfter(self.Refresh) + self.layers = layers + self.scale = scale + self.thickness = thickness + self.size = size + self.interval = interval + self.pause = pause + self.overshoot = overshoot + self.z_axis_rate = z_axis_rate + self.prelift_gcode = prelift_gcode + self.postlift_gcode = postlift_gcode + self.direction = direction + self.layer_red = layer_red + self.offset = offset + self.index = 0 + self.running = True + + self.next_img() + +class SettingsFrame(wx.Frame): + + def _set_setting(self, name, value): + if self.pronterface: + self.pronterface.set(name, value) + + def _get_setting(self, name, val): + if self.pronterface: + try: + return getattr(self.pronterface.settings, name) + except AttributeError: + return val + else: + return val + + def __init__(self, parent, printer = None): + wx.Frame.__init__(self, parent, title = "ProjectLayer Control", style = (wx.DEFAULT_FRAME_STYLE | wx.WS_EX_CONTEXTHELP)) + self.SetExtraStyle(wx.FRAME_EX_CONTEXTHELP) + self.pronterface = parent + self.display_frame = DisplayFrame(self, title = "ProjectLayer Display", printer = printer) + + self.panel = wx.Panel(self) + + vbox = wx.BoxSizer(wx.VERTICAL) + buttonbox = wx.StaticBoxSizer(wx.StaticBox(self.panel, label = "Controls"), wx.HORIZONTAL) + + load_button = wx.Button(self.panel, -1, "Load") + load_button.Bind(wx.EVT_BUTTON, self.load_file) + load_button.SetHelpText("Choose an SVG file created from Slic3r or Skeinforge, or a zip file of bitmap images (with extension: .3dlp.zip).") + buttonbox.Add(load_button, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5) + + present_button = wx.Button(self.panel, -1, "Present") + present_button.Bind(wx.EVT_BUTTON, self.start_present) + present_button.SetHelpText("Starts the presentation of the slices.") + buttonbox.Add(present_button, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5) + + self.pause_button = wx.Button(self.panel, -1, "Pause") + self.pause_button.Bind(wx.EVT_BUTTON, self.pause_present) + self.pause_button.SetHelpText("Pauses the presentation. Can be resumed afterwards by clicking this button, or restarted by clicking present again.") + buttonbox.Add(self.pause_button, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5) + + stop_button = wx.Button(self.panel, -1, "Stop") + stop_button.Bind(wx.EVT_BUTTON, self.stop_present) + stop_button.SetHelpText("Stops presenting the slices.") + buttonbox.Add(stop_button, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5) + + self.help_button = wx.ContextHelpButton(self.panel) + buttonbox.Add(self.help_button, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5) + + fieldboxsizer = wx.StaticBoxSizer(wx.StaticBox(self.panel, label = "Settings"), wx.VERTICAL) + fieldsizer = wx.GridBagSizer(10, 10) + + # Left Column + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Layer (mm):"), pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL) + self.thickness = wx.TextCtrl(self.panel, -1, str(self._get_setting("project_layer", "0.1")), size = (80, -1)) + self.thickness.Bind(wx.EVT_TEXT, self.update_thickness) + self.thickness.SetHelpText("The thickness of each slice. Should match the value used to slice the model. SVG files update this value automatically, 3dlp.zip files have to be manually entered.") + fieldsizer.Add(self.thickness, pos = (0, 1)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Exposure (s):"), pos = (1, 0), flag = wx.ALIGN_CENTER_VERTICAL) + self.interval = wx.TextCtrl(self.panel, -1, str(self._get_setting("project_interval", "0.5")), size = (80, -1)) + self.interval.Bind(wx.EVT_TEXT, self.update_interval) + self.interval.SetHelpText("How long each slice should be displayed.") + fieldsizer.Add(self.interval, pos = (1, 1)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Blank (s):"), pos = (2, 0), flag = wx.ALIGN_CENTER_VERTICAL) + self.pause = wx.TextCtrl(self.panel, -1, str(self._get_setting("project_pause", "0.5")), size = (80, -1)) + self.pause.Bind(wx.EVT_TEXT, self.update_pause) + self.pause.SetHelpText("The pause length between slices. This should take into account any movement of the Z axis, plus time to prepare the resin surface (sliding, tilting, sweeping, etc).") + fieldsizer.Add(self.pause, pos = (2, 1)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Scale:"), pos = (3, 0), flag = wx.ALIGN_CENTER_VERTICAL) + self.scale = floatspin.FloatSpin(self.panel, -1, value = self._get_setting('project_scale', 1.0), increment = 0.1, digits = 3, size = (80, -1)) + self.scale.Bind(floatspin.EVT_FLOATSPIN, self.update_scale) + self.scale.SetHelpText("The additional scaling of each slice.") + fieldsizer.Add(self.scale, pos = (3, 1)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Direction:"), pos = (4, 0), flag = wx.ALIGN_CENTER_VERTICAL) + self.direction = wx.ComboBox(self.panel, -1, choices = ["Top Down", "Bottom Up"], value = self._get_setting('project_direction', "Top Down"), size = (80, -1)) + self.direction.Bind(wx.EVT_COMBOBOX, self.update_direction) + self.direction.SetHelpText("The direction the Z axis should move. Top Down is where the projector is above the model, Bottom up is where the projector is below the model.") + fieldsizer.Add(self.direction, pos = (4, 1), flag = wx.ALIGN_CENTER_VERTICAL) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Overshoot (mm):"), pos = (5, 0), flag = wx.ALIGN_CENTER_VERTICAL) + self.overshoot = floatspin.FloatSpin(self.panel, -1, value = self._get_setting('project_overshoot', 3.0), increment = 0.1, digits = 1, min_val = 0, size = (80, -1)) + self.overshoot.Bind(floatspin.EVT_FLOATSPIN, self.update_overshoot) + self.overshoot.SetHelpText("How far the axis should move beyond the next slice position for each slice. For Top Down printers this would dunk the model under the resi and then return. For Bottom Up printers this would raise the base away from the vat and then return.") + fieldsizer.Add(self.overshoot, pos = (5, 1)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Pre-lift Gcode:"), pos = (6, 0), flag = wx.ALIGN_CENTER_VERTICAL) + self.prelift_gcode = wx.TextCtrl(self.panel, -1, str(self._get_setting("project_prelift_gcode", "").replace("\\n", '\n')), size = (-1, 35), style = wx.TE_MULTILINE) + self.prelift_gcode.SetHelpText("Additional gcode to run before raising the Z axis. Be sure to take into account any additional time needed in the pause value, and be careful what gcode is added!") + self.prelift_gcode.Bind(wx.EVT_TEXT, self.update_prelift_gcode) + fieldsizer.Add(self.prelift_gcode, pos = (6, 1), span = (2, 1)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Post-lift Gcode:"), pos = (6, 2), flag = wx.ALIGN_CENTER_VERTICAL) + self.postlift_gcode = wx.TextCtrl(self.panel, -1, str(self._get_setting("project_postlift_gcode", "").replace("\\n", '\n')), size = (-1, 35), style = wx.TE_MULTILINE) + self.postlift_gcode.SetHelpText("Additional gcode to run after raising the Z axis. Be sure to take into account any additional time needed in the pause value, and be careful what gcode is added!") + self.postlift_gcode.Bind(wx.EVT_TEXT, self.update_postlift_gcode) + fieldsizer.Add(self.postlift_gcode, pos = (6, 3), span = (2, 1)) + + # Right Column + + fieldsizer.Add(wx.StaticText(self.panel, -1, "X (px):"), pos = (0, 2), flag = wx.ALIGN_CENTER_VERTICAL) + projectX = int(math.floor(float(self._get_setting("project_x", 1920)))) + self.X = wx.SpinCtrl(self.panel, -1, str(projectX), max = 999999, size = (80, -1)) + self.X.Bind(wx.EVT_SPINCTRL, self.update_resolution) + self.X.SetHelpText("The projector resolution in the X axis.") + fieldsizer.Add(self.X, pos = (0, 3)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Y (px):"), pos = (1, 2), flag = wx.ALIGN_CENTER_VERTICAL) + projectY = int(math.floor(float(self._get_setting("project_y", 1200)))) + self.Y = wx.SpinCtrl(self.panel, -1, str(projectY), max = 999999, size = (80, -1)) + self.Y.Bind(wx.EVT_SPINCTRL, self.update_resolution) + self.Y.SetHelpText("The projector resolution in the Y axis.") + fieldsizer.Add(self.Y, pos = (1, 3)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "OffsetX (mm):"), pos = (2, 2), flag = wx.ALIGN_CENTER_VERTICAL) + self.offset_X = floatspin.FloatSpin(self.panel, -1, value = self._get_setting("project_offset_x", 0.0), increment = 1, digits = 1, size = (80, -1)) + self.offset_X.Bind(floatspin.EVT_FLOATSPIN, self.update_offset) + self.offset_X.SetHelpText("How far the slice should be offset from the edge in the X axis.") + fieldsizer.Add(self.offset_X, pos = (2, 3)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "OffsetY (mm):"), pos = (3, 2), flag = wx.ALIGN_CENTER_VERTICAL) + self.offset_Y = floatspin.FloatSpin(self.panel, -1, value = self._get_setting("project_offset_y", 0.0), increment = 1, digits = 1, size = (80, -1)) + self.offset_Y.Bind(floatspin.EVT_FLOATSPIN, self.update_offset) + self.offset_Y.SetHelpText("How far the slice should be offset from the edge in the Y axis.") + fieldsizer.Add(self.offset_Y, pos = (3, 3)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "ProjectedX (mm):"), pos = (4, 2), flag = wx.ALIGN_CENTER_VERTICAL) + self.projected_X_mm = floatspin.FloatSpin(self.panel, -1, value = self._get_setting("project_projected_x", 505.0), increment = 1, digits = 1, size = (80, -1)) + self.projected_X_mm.Bind(floatspin.EVT_FLOATSPIN, self.update_projected_Xmm) + self.projected_X_mm.SetHelpText("The actual width of the entire projected image. Use the Calibrate grid to show the full size of the projected image, and measure the width at the same level where the slice will be projected onto the resin.") + fieldsizer.Add(self.projected_X_mm, pos = (4, 3)) + + fieldsizer.Add(wx.StaticText(self.panel, -1, "Z Axis Speed (mm/min):"), pos = (5, 2), flag = wx.ALIGN_CENTER_VERTICAL) + self.z_axis_rate = wx.SpinCtrl(self.panel, -1, str(self._get_setting("project_z_axis_rate", 200)), max = 9999, size = (80, -1)) + self.z_axis_rate.Bind(wx.EVT_SPINCTRL, self.update_z_axis_rate) + self.z_axis_rate.SetHelpText("Speed of the Z axis in mm/minute. Take into account that slower rates may require a longer pause value.") + fieldsizer.Add(self.z_axis_rate, pos = (5, 3)) + + fieldboxsizer.Add(fieldsizer) + + # Display + + displayboxsizer = wx.StaticBoxSizer(wx.StaticBox(self.panel, label = "Display"), wx.VERTICAL) + displaysizer = wx.GridBagSizer(10, 10) + + displaysizer.Add(wx.StaticText(self.panel, -1, "Fullscreen:"), pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL) + self.fullscreen = wx.CheckBox(self.panel, -1) + self.fullscreen.Bind(wx.EVT_CHECKBOX, self.update_fullscreen) + self.fullscreen.SetHelpText("Toggles the project screen to full size.") + displaysizer.Add(self.fullscreen, pos = (0, 1), flag = wx.ALIGN_CENTER_VERTICAL) + + displaysizer.Add(wx.StaticText(self.panel, -1, "Calibrate:"), pos = (0, 2), flag = wx.ALIGN_CENTER_VERTICAL) + self.calibrate = wx.CheckBox(self.panel, -1) + self.calibrate.Bind(wx.EVT_CHECKBOX, self.show_calibrate) + self.calibrate.SetHelpText("Toggles the calibration grid. Each grid should be 10mmx10mm in size. Use the grid to ensure the projected size is correct. See also the help for the ProjectedX field.") + displaysizer.Add(self.calibrate, pos = (0, 3), flag = wx.ALIGN_CENTER_VERTICAL) + + displaysizer.Add(wx.StaticText(self.panel, -1, "1st Layer:"), pos = (0, 4), flag = wx.ALIGN_CENTER_VERTICAL) + + first_layer_boxer = wx.BoxSizer(wx.HORIZONTAL) + self.first_layer = wx.CheckBox(self.panel, -1) + self.first_layer.Bind(wx.EVT_CHECKBOX, self.show_first_layer) + self.first_layer.SetHelpText("Displays the first layer of the model. Use this to project the first layer for longer so it holds to the base. Note: this value does not affect the first layer when the \"Present\" run is started, it should be used manually.") + + first_layer_boxer.Add(self.first_layer, flag = wx.ALIGN_CENTER_VERTICAL) + + first_layer_boxer.Add(wx.StaticText(self.panel, -1, " (s):"), flag = wx.ALIGN_CENTER_VERTICAL) + self.show_first_layer_timer = floatspin.FloatSpin(self.panel, -1, value=-1, increment = 1, digits = 1, size = (55, -1)) + self.show_first_layer_timer.SetHelpText("How long to display the first layer for. -1 = unlimited.") + first_layer_boxer.Add(self.show_first_layer_timer, flag = wx.ALIGN_CENTER_VERTICAL) + displaysizer.Add(first_layer_boxer, pos = (0, 6), flag = wx.ALIGN_CENTER_VERTICAL) + + displaysizer.Add(wx.StaticText(self.panel, -1, "Red:"), pos = (0, 7), flag = wx.ALIGN_CENTER_VERTICAL) + self.layer_red = wx.CheckBox(self.panel, -1) + self.layer_red.Bind(wx.EVT_CHECKBOX, self.show_layer_red) + self.layer_red.SetHelpText("Toggles whether the image should be red. Useful for positioning whilst resin is in the printer as it should not cause a reaction.") + displaysizer.Add(self.layer_red, pos = (0, 8), flag = wx.ALIGN_CENTER_VERTICAL) + + displayboxsizer.Add(displaysizer) + + # Info + infosizer = wx.StaticBoxSizer(wx.StaticBox(self.panel, label = "Info"), wx.VERTICAL) + + infofieldsizer = wx.GridBagSizer(10, 10) + + filelabel = wx.StaticText(self.panel, -1, "File:") + filelabel.SetHelpText("The name of the model currently loaded.") + infofieldsizer.Add(filelabel, pos = (0, 0)) + self.filename = wx.StaticText(self.panel, -1, "") + + infofieldsizer.Add(self.filename, pos = (0, 1)) + + totallayerslabel = wx.StaticText(self.panel, -1, "Total Layers:") + totallayerslabel.SetHelpText("The total number of layers found in the model.") + infofieldsizer.Add(totallayerslabel, pos = (1, 0)) + self.total_layers = wx.StaticText(self.panel, -1) + + infofieldsizer.Add(self.total_layers, pos = (1, 1)) + + currentlayerlabel = wx.StaticText(self.panel, -1, "Current Layer:") + currentlayerlabel.SetHelpText("The current layer being displayed.") + infofieldsizer.Add(currentlayerlabel, pos = (2, 0)) + self.current_layer = wx.StaticText(self.panel, -1, "0") + infofieldsizer.Add(self.current_layer, pos = (2, 1)) + + estimatedtimelabel = wx.StaticText(self.panel, -1, "Estimated Time:") + estimatedtimelabel.SetHelpText("An estimate of the remaining time until print completion.") + infofieldsizer.Add(estimatedtimelabel, pos = (3, 0)) + self.estimated_time = wx.StaticText(self.panel, -1, "") + infofieldsizer.Add(self.estimated_time, pos = (3, 1)) + + infosizer.Add(infofieldsizer) + + # + + vbox.Add(buttonbox, flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, border = 10) + vbox.Add(fieldboxsizer, flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 10) + vbox.Add(displayboxsizer, flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 10) + vbox.Add(infosizer, flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 10) + + self.panel.SetSizer(vbox) + self.panel.Fit() + self.Fit() + self.SetPosition((0, 0)) + self.Show() + + def __del__(self): + if hasattr(self, 'image_dir') and self.image_dir != '': + shutil.rmtree(self.image_dir) + if self.display_frame: + self.display_frame.Destroy() + + def set_total_layers(self, total): + self.total_layers.SetLabel(str(total)) + self.set_estimated_time() + + def set_current_layer(self, index): + self.current_layer.SetLabel(str(index)) + self.set_estimated_time() + + def display_filename(self, name): + self.filename.SetLabel(name) + + def set_estimated_time(self): + if not hasattr(self, 'layers'): + return + + current_layer = int(self.current_layer.GetLabel()) + remaining_layers = len(self.layers[0]) - current_layer + # 0.5 for delay between hide and rise + estimated_time = remaining_layers * (float(self.interval.GetValue()) + float(self.pause.GetValue()) + 0.5) + self.estimated_time.SetLabel(time.strftime("%H:%M:%S", time.gmtime(estimated_time))) + + def parse_svg(self, name): + et = xml.etree.ElementTree.ElementTree(file = name) + # xml.etree.ElementTree.dump(et) + + slicer = 'Slic3r' if et.getroot().find('{http://www.w3.org/2000/svg}metadata') is None else 'Skeinforge' + zlast = 0 + zdiff = 0 + ol = [] + if (slicer == 'Slic3r'): + height = et.getroot().get('height').replace('m', '') + width = et.getroot().get('width').replace('m', '') + + for i in et.findall("{http://www.w3.org/2000/svg}g"): + z = float(i.get('{http://slic3r.org/namespaces/slic3r}z')) + zdiff = z - zlast + zlast = z + + svgSnippet = xml.etree.ElementTree.Element('{http://www.w3.org/2000/svg}svg') + svgSnippet.set('height', height + 'mm') + svgSnippet.set('width', width + 'mm') + + svgSnippet.set('viewBox', '0 0 ' + width + ' ' + height) + svgSnippet.set('style', 'background-color:black;fill:white;') + svgSnippet.append(i) + + ol += [svgSnippet] + else: + + slice_layers = et.findall("{http://www.w3.org/2000/svg}metadata")[0].findall("{http://www.reprap.org/slice}layers")[0] + minX = slice_layers.get('minX') + maxX = slice_layers.get('maxX') + minY = slice_layers.get('minY') + maxY = slice_layers.get('maxY') + + height = str(abs(float(minY)) + abs(float(maxY))) + width = str(abs(float(minX)) + abs(float(maxX))) + + for g in et.findall("{http://www.w3.org/2000/svg}g")[0].findall("{http://www.w3.org/2000/svg}g"): + + g.set('transform', '') + + text_element = g.findall("{http://www.w3.org/2000/svg}text")[0] + g.remove(text_element) + + path_elements = g.findall("{http://www.w3.org/2000/svg}path") + for p in path_elements: + p.set('transform', 'translate(' + maxX + ',' + maxY + ')') + p.set('fill', 'white') + + z = float(g.get('id').split("z:")[-1]) + zdiff = z - zlast + zlast = z + + svgSnippet = xml.etree.ElementTree.Element('{http://www.w3.org/2000/svg}svg') + svgSnippet.set('height', height + 'mm') + svgSnippet.set('width', width + 'mm') + + svgSnippet.set('viewBox', '0 0 ' + width + ' ' + height) + svgSnippet.set('style', 'background-color:black;fill:white;') + svgSnippet.append(g) + + ol += [svgSnippet] + return ol, zdiff, slicer + + def parse_3DLP_zip(self, name): + if not zipfile.is_zipfile(name): + raise Exception(name + " is not a zip file!") + accepted_image_types = ['gif', 'tiff', 'jpg', 'jpeg', 'bmp', 'png'] + zipFile = zipfile.ZipFile(name, 'r') + self.image_dir = tempfile.mkdtemp() + zipFile.extractall(self.image_dir) + ol = [] + + # Note: the following funky code extracts any numbers from the filenames, matches + # them with the original then sorts them. It allows for filenames of the + # format: abc_1.png, which would be followed by abc_10.png alphabetically. + os.chdir(self.image_dir) + vals = filter(os.path.isfile, os.listdir('.')) + keys = map(lambda p: int(re.search('\d+', p).group()), vals) + imagefilesDict = dict(itertools.izip(keys, vals)) + imagefilesOrderedDict = OrderedDict(sorted(imagefilesDict.items(), key = lambda t: t[0])) + + for f in imagefilesOrderedDict.values(): + path = os.path.join(self.image_dir, f) + if os.path.isfile(path) and imghdr.what(path) in accepted_image_types: + ol.append(path) + + return ol, -1, "bitmap" + + def load_file(self, event): + dlg = wx.FileDialog(self, ("Open file to print"), style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + dlg.SetWildcard(("Slic3r or Skeinforge svg files (;*.svg;*.SVG;);3DLP Zip (;*.3dlp.zip;)")) + if dlg.ShowModal() == wx.ID_OK: + name = dlg.GetPath() + if not(os.path.exists(name)): + self.status.SetStatusText(("File not found!")) + return + if name.endswith(".3dlp.zip"): + layers = self.parse_3DLP_zip(name) + layerHeight = float(self.thickness.GetValue()) + else: + layers = self.parse_svg(name) + layerHeight = layers[1] + self.thickness.SetValue(str(layers[1])) + print "Layer thickness detected:", layerHeight, "mm" + print len(layers[0]), "layers found, total height", layerHeight * len(layers[0]), "mm" + self.layers = layers + self.set_total_layers(len(layers[0])) + self.set_current_layer(0) + self.current_filename = os.path.basename(name) + self.display_filename(self.current_filename) + self.slicer = layers[2] + self.display_frame.slicer = self.slicer + dlg.Destroy() + + def show_calibrate(self, event): + if self.calibrate.IsChecked(): + self.present_calibrate(event) + else: + if hasattr(self, 'layers'): + self.display_frame.slicer = self.layers[2] + self.display_frame.scale = float(self.scale.GetValue()) + self.display_frame.clear_layer() + + def show_first_layer(self, event): + if self.first_layer.IsChecked(): + self.present_first_layer(event) + else: + if hasattr(self, 'layers'): + self.display_frame.slicer = self.layers[2] + self.display_frame.scale = float(self.scale.GetValue()) + self.display_frame.clear_layer() + + def show_layer_red(self, event): + self.display_frame.layer_red = self.layer_red.IsChecked() + + def present_calibrate(self, event): + if self.calibrate.IsChecked(): + self.display_frame.Raise() + self.display_frame.offset = (float(self.offset_X.GetValue()), -float(self.offset_Y.GetValue())) + self.display_frame.scale = 1.0 + resolution_x_pixels = int(self.X.GetValue()) + resolution_y_pixels = int(self.Y.GetValue()) + + gridBitmap = wx.EmptyBitmap(resolution_x_pixels, resolution_y_pixels) + dc = wx.MemoryDC() + dc.SelectObject(gridBitmap) + dc.SetBackground(wx.Brush("black")) + dc.Clear() + + dc.SetPen(wx.Pen("red", 7)) + dc.DrawLine(0, 0, resolution_x_pixels, 0) + dc.DrawLine(0, 0, 0, resolution_y_pixels) + dc.DrawLine(resolution_x_pixels, 0, resolution_x_pixels, resolution_y_pixels) + dc.DrawLine(0, resolution_y_pixels, resolution_x_pixels, resolution_y_pixels) + + dc.SetPen(wx.Pen("red", 2)) + aspectRatio = float(resolution_x_pixels) / float(resolution_y_pixels) + + projectedXmm = float(self.projected_X_mm.GetValue()) + projectedYmm = round(projectedXmm / aspectRatio) + + pixelsXPerMM = resolution_x_pixels / projectedXmm + pixelsYPerMM = resolution_y_pixels / projectedYmm + + gridCountX = int(projectedXmm / 10) + gridCountY = int(projectedYmm / 10) + + for y in xrange(0, gridCountY + 1): + for x in xrange(0, gridCountX + 1): + dc.DrawLine(0, y * (pixelsYPerMM * 10), resolution_x_pixels, y * (pixelsYPerMM * 10)) + dc.DrawLine(x * (pixelsXPerMM * 10), 0, x * (pixelsXPerMM * 10), resolution_y_pixels) + + self.first_layer.SetValue(False) + self.display_frame.slicer = 'bitmap' + self.display_frame.draw_layer(gridBitmap.ConvertToImage()) + + def present_first_layer(self, event): + if (self.first_layer.GetValue()): + if not hasattr(self, "layers"): + print "No model loaded!" + self.first_layer.SetValue(False) + return + self.display_frame.offset = (float(self.offset_X.GetValue()), float(self.offset_Y.GetValue())) + self.display_frame.scale = float(self.scale.GetValue()) + + self.display_frame.slicer = self.layers[2] + self.display_frame.dpi = self.get_dpi() + self.display_frame.draw_layer(copy.deepcopy(self.layers[0][0])) + self.calibrate.SetValue(False) + if self.show_first_layer_timer != -1.0: + def unpresent_first_layer(): + self.display_frame.clear_layer() + self.first_layer.SetValue(False) + wx.CallLater(self.show_first_layer_timer.GetValue() * 1000, unpresent_first_layer) + + def update_offset(self, event): + + offset_x = float(self.offset_X.GetValue()) + offset_y = float(self.offset_Y.GetValue()) + self.display_frame.offset = (offset_x, offset_y) + + self._set_setting('project_offset_x', offset_x) + self._set_setting('project_offset_y', offset_y) + + self.refresh_display(event) + + def refresh_display(self, event): + self.present_calibrate(event) + self.present_first_layer(event) + + def update_thickness(self, event): + self._set_setting('project_layer', self.thickness.GetValue()) + self.refresh_display(event) + + def update_projected_Xmm(self, event): + self._set_setting('project_projected_x', self.projected_X_mm.GetValue()) + self.refresh_display(event) + + def update_scale(self, event): + scale = float(self.scale.GetValue()) + self.display_frame.scale = scale + self._set_setting('project_scale', scale) + self.refresh_display(event) + + def update_interval(self, event): + interval = float(self.interval.GetValue()) + self.display_frame.interval = interval + self._set_setting('project_interval', interval) + self.set_estimated_time() + self.refresh_display(event) + + def update_pause(self, event): + pause = float(self.pause.GetValue()) + self.display_frame.pause = pause + self._set_setting('project_pause', pause) + self.set_estimated_time() + self.refresh_display(event) + + def update_overshoot(self, event): + overshoot = float(self.overshoot.GetValue()) + self.display_frame.pause = overshoot + self._set_setting('project_overshoot', overshoot) + + def update_prelift_gcode(self, event): + prelift_gcode = self.prelift_gcode.GetValue().replace('\n', "\\n") + self.display_frame.prelift_gcode = prelift_gcode + self._set_setting('project_prelift_gcode', prelift_gcode) + + def update_postlift_gcode(self, event): + postlift_gcode = self.postlift_gcode.GetValue().replace('\n', "\\n") + self.display_frame.postlift_gcode = postlift_gcode + self._set_setting('project_postlift_gcode', postlift_gcode) + + def update_z_axis_rate(self, event): + z_axis_rate = int(self.z_axis_rate.GetValue()) + self.display_frame.z_axis_rate = z_axis_rate + self._set_setting('project_z_axis_rate', z_axis_rate) + + def update_direction(self, event): + direction = self.direction.GetValue() + self.display_frame.direction = direction + self._set_setting('project_direction', direction) + + def update_fullscreen(self, event): + if (self.fullscreen.GetValue()): + self.display_frame.ShowFullScreen(1) + else: + self.display_frame.ShowFullScreen(0) + self.refresh_display(event) + + def update_resolution(self, event): + x = int(self.X.GetValue()) + y = int(self.Y.GetValue()) + self.display_frame.resize((x, y)) + self._set_setting('project_x', x) + self._set_setting('project_y', y) + self.refresh_display(event) + + def get_dpi(self): + resolution_x_pixels = int(self.X.GetValue()) + projected_x_mm = float(self.projected_X_mm.GetValue()) + projected_x_inches = projected_x_mm / 25.4 + + return resolution_x_pixels / projected_x_inches + + def start_present(self, event): + if not hasattr(self, "layers"): + print "No model loaded!" + return + + self.pause_button.SetLabel("Pause") + self.set_current_layer(0) + self.display_frame.Raise() + if (self.fullscreen.GetValue()): + self.display_frame.ShowFullScreen(1) + self.display_frame.slicer = self.layers[2] + self.display_frame.dpi = self.get_dpi() + self.display_frame.present(self.layers[0][:], + thickness = float(self.thickness.GetValue()), + interval = float(self.interval.GetValue()), + scale = float(self.scale.GetValue()), + pause = float(self.pause.GetValue()), + overshoot = float(self.overshoot.GetValue()), + z_axis_rate = int(self.z_axis_rate.GetValue()), + prelift_gcode = self.prelift_gcode.GetValue(), + postlift_gcode = self.postlift_gcode.GetValue(), + direction = self.direction.GetValue(), + size = (float(self.X.GetValue()), float(self.Y.GetValue())), + offset = (float(self.offset_X.GetValue()), float(self.offset_Y.GetValue())), + layer_red = self.layer_red.IsChecked()) + + def stop_present(self, event): + print "Stop" + self.pause_button.SetLabel("Pause") + self.set_current_layer(0) + self.display_frame.running = False + + def pause_present(self, event): + if self.pause_button.GetLabel() == 'Pause': + print "Pause" + self.pause_button.SetLabel("Continue") + self.display_frame.running = False + else: + print "Continue" + self.pause_button.SetLabel("Pause") + self.display_frame.running = True + self.display_frame.next_img() + +if __name__ == "__main__": + provider = wx.SimpleHelpProvider() + wx.HelpProvider_Set(provider) + a = wx.App() + SettingsFrame(None).Show() + a.MainLoop() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/pronsole.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/pronsole.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,1674 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import cmd +import glob +import os +import time +import threading +import sys +import shutil +import subprocess +import codecs +import argparse +import locale +import logging +import traceback +import re + +from serial import SerialException + +from . import printcore +from .utils import install_locale, run_command, get_command_output, \ + format_time, format_duration, RemainingTimeEstimator, \ + get_home_pos, parse_build_dimensions, parse_temperature_report, \ + setup_logging +install_locale('pronterface') +from .settings import Settings, BuildDimensionsSetting +from .power import powerset_print_start, powerset_print_stop +from printrun import gcoder +from .rpc import ProntRPC + +if os.name == "nt": + try: + import _winreg + except: + pass +READLINE = True +try: + import readline + try: + readline.rl.mode.show_all_if_ambiguous = "on" # config pyreadline on windows + except: + pass +except: + READLINE = False # neither readline module is available + +tempreading_exp = re.compile("(^T:| T:)") + +REPORT_NONE = 0 +REPORT_POS = 1 +REPORT_TEMP = 2 +REPORT_MANUAL = 4 + +class Status(object): + + def __init__(self): + self.extruder_temp = 0 + self.extruder_temp_target = 0 + self.bed_temp = 0 + self.bed_temp_target = 0 + self.print_job = None + self.print_job_progress = 1.0 + + def update_tempreading(self, tempstr): + temps = parse_temperature_report(tempstr) + if "T0" in temps and temps["T0"][0]: hotend_temp = float(temps["T0"][0]) + elif "T" in temps and temps["T"][0]: hotend_temp = float(temps["T"][0]) + else: hotend_temp = None + if "T0" in temps and temps["T0"][1]: hotend_setpoint = float(temps["T0"][1]) + elif "T" in temps and temps["T"][1]: hotend_setpoint = float(temps["T"][1]) + else: hotend_setpoint = None + if hotend_temp is not None: + self.extruder_temp = hotend_temp + if hotend_setpoint is not None: + self.extruder_temp_target = hotend_setpoint + bed_temp = float(temps["B"][0]) if "B" in temps and temps["B"][0] else None + if bed_temp is not None: + self.bed_temp = bed_temp + setpoint = temps["B"][1] + if setpoint: + self.bed_temp_target = float(setpoint) + + @property + def bed_enabled(self): + return self.bed_temp != 0 + + @property + def extruder_enabled(self): + return self.extruder_temp != 0 + + +class pronsole(cmd.Cmd): + def __init__(self): + cmd.Cmd.__init__(self) + if not READLINE: + self.completekey = None + self.status = Status() + self.dynamic_temp = False + self.compute_eta = None + self.statuscheck = False + self.status_thread = None + self.monitor_interval = 3 + self.p = printcore.printcore() + self.p.recvcb = self.recvcb + self.p.startcb = self.startcb + self.p.endcb = self.endcb + self.p.layerchangecb = self.layer_change_cb + self.p.process_host_command = self.process_host_command + self.recvlisteners = [] + self.in_macro = False + self.p.onlinecb = self.online + self.p.errorcb = self.logError + self.fgcode = None + self.filename = None + self.rpc_server = None + self.curlayer = 0 + self.sdlisting = 0 + self.sdlisting_echo = 0 + self.sdfiles = [] + self.paused = False + self.sdprinting = 0 + self.uploading = 0 # Unused, just for pronterface generalization + self.temps = {"pla": "185", "abs": "230", "off": "0"} + self.bedtemps = {"pla": "60", "abs": "110", "off": "0"} + self.percentdone = 0 + self.posreport = "" + self.tempreadings = "" + self.userm114 = 0 + self.userm105 = 0 + self.m105_waitcycles = 0 + self.macros = {} + self.history_file = "~/.pronsole-history" + self.rc_loaded = False + self.processing_rc = False + self.processing_args = False + self.settings = Settings(self) + self.settings._add(BuildDimensionsSetting("build_dimensions", "200x200x100+0+0+0+0+0+0", _("Build dimensions"), _("Dimensions of Build Platform\n & optional offset of origin\n & optional switch position\n\nExamples:\n XXXxYYY\n XXX,YYY,ZZZ\n XXXxYYYxZZZ+OffX+OffY+OffZ\nXXXxYYYxZZZ+OffX+OffY+OffZ+HomeX+HomeY+HomeZ"), "Printer"), self.update_build_dimensions) + self.settings._port_list = self.scanserial + self.settings._temperature_abs_cb = self.set_temp_preset + self.settings._temperature_pla_cb = self.set_temp_preset + self.settings._bedtemp_abs_cb = self.set_temp_preset + self.settings._bedtemp_pla_cb = self.set_temp_preset + self.update_build_dimensions(None, self.settings.build_dimensions) + self.update_tcp_streaming_mode(None, self.settings.tcp_streaming_mode) + self.monitoring = 0 + self.starttime = 0 + self.extra_print_time = 0 + self.silent = False + self.commandprefixes = 'MGT$' + self.promptstrs = {"offline": "%(bold)soffline>%(normal)s ", + "fallback": "%(bold)sPC>%(normal)s ", + "macro": "%(bold)s..>%(normal)s ", + "online": "%(bold)sT:%(extruder_temp_fancy)s%(progress_fancy)s>%(normal)s "} + + # -------------------------------------------------------------- + # General console handling + # -------------------------------------------------------------- + + def postloop(self): + self.p.disconnect() + cmd.Cmd.postloop(self) + + def preloop(self): + self.log(_("Welcome to the printer console! Type \"help\" for a list of available commands.")) + self.prompt = self.promptf() + cmd.Cmd.preloop(self) + + # We replace this function, defined in cmd.py . + # It's default behavior with regards to Ctr-C + # and Ctr-D doesn't make much sense... + def cmdloop(self, intro=None): + """Repeatedly issue a prompt, accept input, parse an initial prefix + off the received input, and dispatch to action methods, passing them + the remainder of the line as argument. + + """ + + self.preloop() + if self.use_rawinput and self.completekey: + try: + import readline + self.old_completer = readline.get_completer() + readline.set_completer(self.complete) + readline.parse_and_bind(self.completekey + ": complete") + history = os.path.expanduser(self.history_file) + if os.path.exists(history): + readline.read_history_file(history) + except ImportError: + pass + try: + if intro is not None: + self.intro = intro + if self.intro: + self.stdout.write(str(self.intro) + "\n") + stop = None + while not stop: + if self.cmdqueue: + line = self.cmdqueue.pop(0) + else: + if self.use_rawinput: + try: + line = raw_input(self.prompt) + except EOFError: + self.log("") + self.do_exit("") + except KeyboardInterrupt: + self.log("") + line = "" + else: + self.stdout.write(self.prompt) + self.stdout.flush() + line = self.stdin.readline() + if not len(line): + line = "" + else: + line = line.rstrip('\r\n') + line = self.precmd(line) + stop = self.onecmd(line) + stop = self.postcmd(stop, line) + self.postloop() + finally: + if self.use_rawinput and self.completekey: + try: + import readline + readline.set_completer(self.old_completer) + readline.write_history_file(history) + except ImportError: + pass + + def confirm(self): + y_or_n = raw_input("y/n: ") + if y_or_n == "y": + return True + elif y_or_n != "n": + return self.confirm() + return False + + def log(self, *msg): + msg = u"".join(unicode(i) for i in msg) + logging.info(msg) + + def logError(self, *msg): + msg = u"".join(unicode(i) for i in msg) + logging.error(msg) + if not self.settings.error_command: + return + output = get_command_output(self.settings.error_command, {"$m": msg}) + if output: + self.log("Error command output:") + self.log(output.rstrip()) + + def promptf(self): + """A function to generate prompts so that we can do dynamic prompts. """ + if self.in_macro: + promptstr = self.promptstrs["macro"] + elif not self.p.online: + promptstr = self.promptstrs["offline"] + elif self.status.extruder_enabled: + promptstr = self.promptstrs["online"] + else: + promptstr = self.promptstrs["fallback"] + if "%" not in promptstr: + return promptstr + else: + specials = {} + specials["extruder_temp"] = str(int(self.status.extruder_temp)) + specials["extruder_temp_target"] = str(int(self.status.extruder_temp_target)) + if self.status.extruder_temp_target == 0: + specials["extruder_temp_fancy"] = str(int(self.status.extruder_temp)) + else: + specials["extruder_temp_fancy"] = "%s/%s" % (str(int(self.status.extruder_temp)), str(int(self.status.extruder_temp_target))) + if self.p.printing: + progress = int(1000 * float(self.p.queueindex) / len(self.p.mainqueue)) / 10 + elif self.sdprinting: + progress = self.percentdone + else: + progress = 0.0 + specials["progress"] = str(progress) + if self.p.printing or self.sdprinting: + specials["progress_fancy"] = " " + str(progress) + "%" + else: + specials["progress_fancy"] = "" + specials["bold"] = "\033[01m" + specials["normal"] = "\033[00m" + return promptstr % specials + + def postcmd(self, stop, line): + """ A hook we override to generate prompts after + each command is executed, for the next prompt. + We also use it to send M105 commands so that + temp info gets updated for the prompt.""" + if self.p.online and self.dynamic_temp: + self.p.send_now("M105") + self.prompt = self.promptf() + return stop + + def kill(self): + self.statuscheck = False + if self.status_thread: + self.status_thread.join() + self.status_thread = None + if self.rpc_server is not None: + self.rpc_server.shutdown() + + def write_prompt(self): + sys.stdout.write(self.promptf()) + sys.stdout.flush() + + def help_help(self, l = ""): + self.do_help("") + + def do_gcodes(self, l = ""): + self.help_gcodes() + + def help_gcodes(self): + self.log("Gcodes are passed through to the printer as they are") + + def precmd(self, line): + if line.upper().startswith("M114"): + self.userm114 += 1 + elif line.upper().startswith("M105"): + self.userm105 += 1 + return line + + def help_shell(self): + self.log("Executes a python command. Example:") + self.log("! os.listdir('.')") + + def do_shell(self, l): + exec(l) + + def emptyline(self): + """Called when an empty line is entered - do not remove""" + pass + + def default(self, l): + if l[0].upper() in self.commandprefixes.upper(): + if self.p and self.p.online: + if not self.p.loud: + self.log("SENDING:" + l.upper()) + self.p.send_now(l.upper()) + else: + self.logError(_("Printer is not online.")) + return + elif l[0] == "@": + if self.p and self.p.online: + if not self.p.loud: + self.log("SENDING:" + l[1:]) + self.p.send_now(l[1:]) + else: + self.logError(_("Printer is not online.")) + return + else: + cmd.Cmd.default(self, l) + + def do_exit(self, l): + if self.status.extruder_temp_target != 0: + self.log("Setting extruder temp to 0") + self.p.send_now("M104 S0.0") + if self.status.bed_enabled: + if self.status.bed_temp_target != 0: + self.log("Setting bed temp to 0") + self.p.send_now("M140 S0.0") + self.log("Disconnecting from printer...") + if self.p.printing: + self.log(_("Are you sure you want to exit while printing?\n\ +(this will terminate the print).")) + if not self.confirm(): + return + self.log(_("Exiting program. Goodbye!")) + self.p.disconnect() + self.kill() + sys.exit() + + def help_exit(self): + self.log(_("Disconnects from the printer and exits the program.")) + + # -------------------------------------------------------------- + # Macro handling + # -------------------------------------------------------------- + + def complete_macro(self, text, line, begidx, endidx): + if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): + return [i for i in self.macros.keys() if i.startswith(text)] + elif len(line.split()) == 3 or (len(line.split()) == 2 and line[-1] == " "): + return [i for i in ["/D", "/S"] + self.completenames(text) if i.startswith(text)] + else: + return [] + + def hook_macro(self, l): + l = l.rstrip() + ls = l.lstrip() + ws = l[:len(l) - len(ls)] # just leading whitespace + if len(ws) == 0: + self.end_macro() + # pass the unprocessed line to regular command processor to not require empty line in .pronsolerc + return self.onecmd(l) + self.cur_macro_def += l + "\n" + + def end_macro(self): + if "onecmd" in self.__dict__: del self.onecmd # remove override + self.in_macro = False + self.prompt = self.promptf() + if self.cur_macro_def != "": + self.macros[self.cur_macro_name] = self.cur_macro_def + macro = self.compile_macro(self.cur_macro_name, self.cur_macro_def) + setattr(self.__class__, "do_" + self.cur_macro_name, lambda self, largs, macro = macro: macro(self, *largs.split())) + setattr(self.__class__, "help_" + self.cur_macro_name, lambda self, macro_name = self.cur_macro_name: self.subhelp_macro(macro_name)) + if not self.processing_rc: + self.log("Macro '" + self.cur_macro_name + "' defined") + # save it + if not self.processing_args: + macro_key = "macro " + self.cur_macro_name + macro_def = macro_key + if "\n" in self.cur_macro_def: + macro_def += "\n" + else: + macro_def += " " + macro_def += self.cur_macro_def + self.save_in_rc(macro_key, macro_def) + else: + self.logError("Empty macro - cancelled") + del self.cur_macro_name, self.cur_macro_def + + def compile_macro_line(self, line): + line = line.rstrip() + ls = line.lstrip() + ws = line[:len(line) - len(ls)] # just leading whitespace + if ls == "" or ls.startswith('#'): return "" # no code + if ls.startswith('!'): + return ws + ls[1:] + "\n" # python mode + else: + ls = ls.replace('"', '\\"') # need to escape double quotes + ret = ws + 'self.precmd("' + ls + '".format(*arg))\n' # parametric command mode + return ret + ws + 'self.onecmd("' + ls + '".format(*arg))\n' + + def compile_macro(self, macro_name, macro_def): + if macro_def.strip() == "": + self.logError("Empty macro - cancelled") + return + macro = None + pycode = "def macro(self,*arg):\n" + if "\n" not in macro_def.strip(): + pycode += self.compile_macro_line(" " + macro_def.strip()) + else: + lines = macro_def.split("\n") + for l in lines: + pycode += self.compile_macro_line(l) + exec pycode + return macro + + def start_macro(self, macro_name, prev_definition = "", suppress_instructions = False): + if not self.processing_rc and not suppress_instructions: + self.logError("Enter macro using indented lines, end with empty line") + self.cur_macro_name = macro_name + self.cur_macro_def = "" + self.onecmd = self.hook_macro # override onecmd temporarily + self.in_macro = False + self.prompt = self.promptf() + + def delete_macro(self, macro_name): + if macro_name in self.macros.keys(): + delattr(self.__class__, "do_" + macro_name) + del self.macros[macro_name] + self.log("Macro '" + macro_name + "' removed") + if not self.processing_rc and not self.processing_args: + self.save_in_rc("macro " + macro_name, "") + else: + self.logError("Macro '" + macro_name + "' is not defined") + + def do_macro(self, args): + if args.strip() == "": + self.print_topics("User-defined macros", map(str, self.macros.keys()), 15, 80) + return + arglist = args.split(None, 1) + macro_name = arglist[0] + if macro_name not in self.macros and hasattr(self.__class__, "do_" + macro_name): + self.logError("Name '" + macro_name + "' is being used by built-in command") + return + if len(arglist) == 2: + macro_def = arglist[1] + if macro_def.lower() == "/d": + self.delete_macro(macro_name) + return + if macro_def.lower() == "/s": + self.subhelp_macro(macro_name) + return + self.cur_macro_def = macro_def + self.cur_macro_name = macro_name + self.end_macro() + return + if macro_name in self.macros: + self.start_macro(macro_name, self.macros[macro_name]) + else: + self.start_macro(macro_name) + + def help_macro(self): + self.log("Define single-line macro: macro ") + self.log("Define multi-line macro: macro ") + self.log("Enter macro definition in indented lines. Use {0} .. {N} to substitute macro arguments") + self.log("Enter python code, prefixed with ! Use arg[0] .. arg[N] to substitute macro arguments") + self.log("Delete macro: macro /d") + self.log("Show macro definition: macro /s") + self.log("'macro' without arguments displays list of defined macros") + + def subhelp_macro(self, macro_name): + if macro_name in self.macros.keys(): + macro_def = self.macros[macro_name] + if "\n" in macro_def: + self.log("Macro '" + macro_name + "' defined as:") + self.log(self.macros[macro_name] + "----------------") + else: + self.log("Macro '" + macro_name + "' defined as: '" + macro_def + "'") + else: + self.logError("Macro '" + macro_name + "' is not defined") + + # -------------------------------------------------------------- + # Configuration handling + # -------------------------------------------------------------- + + def set(self, var, str): + try: + t = type(getattr(self.settings, var)) + value = self.settings._set(var, str) + if not self.processing_rc and not self.processing_args: + self.save_in_rc("set " + var, "set %s %s" % (var, value)) + except AttributeError: + logging.debug(_("Unknown variable '%s'") % var) + except ValueError, ve: + if hasattr(ve, "from_validator"): + self.logError(_("Bad value %s for variable '%s': %s") % (str, var, ve.args[0])) + else: + self.logError(_("Bad value for variable '%s', expecting %s (%s)") % (var, repr(t)[1:-1], ve.args[0])) + + def do_set(self, argl): + args = argl.split(None, 1) + if len(args) < 1: + for k in [kk for kk in dir(self.settings) if not kk.startswith("_")]: + self.log("%s = %s" % (k, str(getattr(self.settings, k)))) + return + if len(args) < 2: + # Try getting the default value of the setting to check whether it + # actually exists + try: + getattr(self.settings, args[0]) + except AttributeError: + logging.warning("Unknown variable '%s'" % args[0]) + return + self.set(args[0], args[1]) + + def help_set(self): + self.log("Set variable: set ") + self.log("Show variable: set ") + self.log("'set' without arguments displays all variables") + + def complete_set(self, text, line, begidx, endidx): + if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): + return [i for i in dir(self.settings) if not i.startswith("_") and i.startswith(text)] + elif len(line.split()) == 3 or (len(line.split()) == 2 and line[-1] == " "): + return [i for i in self.settings._tabcomplete(line.split()[1]) if i.startswith(text)] + else: + return [] + + def load_rc(self, rc_filename): + self.processing_rc = True + try: + rc = codecs.open(rc_filename, "r", "utf-8") + self.rc_filename = os.path.abspath(rc_filename) + for rc_cmd in rc: + if not rc_cmd.lstrip().startswith("#"): + self.onecmd(rc_cmd) + rc.close() + if hasattr(self, "cur_macro_def"): + self.end_macro() + self.rc_loaded = True + finally: + self.processing_rc = False + + def load_default_rc(self, rc_filename = ".pronsolerc"): + if rc_filename == ".pronsolerc" and hasattr(sys, "frozen") and sys.frozen in ["windows_exe", "console_exe"]: + rc_filename = "printrunconf.ini" + try: + try: + self.load_rc(os.path.join(os.path.expanduser("~"), rc_filename)) + except IOError: + self.load_rc(rc_filename) + except IOError: + # make sure the filename is initialized + self.rc_filename = os.path.abspath(os.path.join(os.path.expanduser("~"), rc_filename)) + + def save_in_rc(self, key, definition): + """ + Saves or updates macro or other definitions in .pronsolerc + key is prefix that determines what is being defined/updated (e.g. 'macro foo') + definition is the full definition (that is written to file). (e.g. 'macro foo move x 10') + Set key as empty string to just add (and not overwrite) + Set definition as empty string to remove it from .pronsolerc + To delete line from .pronsolerc, set key as the line contents, and definition as empty string + Only first definition with given key is overwritten. + Updates are made in the same file position. + Additions are made to the end of the file. + """ + rci, rco = None, None + if definition != "" and not definition.endswith("\n"): + definition += "\n" + try: + written = False + if os.path.exists(self.rc_filename): + shutil.copy(self.rc_filename, self.rc_filename + "~bak") + rci = codecs.open(self.rc_filename + "~bak", "r", "utf-8") + rco = codecs.open(self.rc_filename + "~new", "w", "utf-8") + if rci is not None: + overwriting = False + for rc_cmd in rci: + l = rc_cmd.rstrip() + ls = l.lstrip() + ws = l[:len(l) - len(ls)] # just leading whitespace + if overwriting and len(ws) == 0: + overwriting = False + if not written and key != "" and rc_cmd.startswith(key) and (rc_cmd + "\n")[len(key)].isspace(): + overwriting = True + written = True + rco.write(definition) + if not overwriting: + rco.write(rc_cmd) + if not rc_cmd.endswith("\n"): rco.write("\n") + if not written: + rco.write(definition) + if rci is not None: + rci.close() + rco.close() + shutil.move(self.rc_filename + "~new", self.rc_filename) + # if definition != "": + # self.log("Saved '"+key+"' to '"+self.rc_filename+"'") + # else: + # self.log("Removed '"+key+"' from '"+self.rc_filename+"'") + except Exception, e: + self.logError("Saving failed for ", key + ":", str(e)) + finally: + del rci, rco + + # -------------------------------------------------------------- + # Configuration update callbacks + # -------------------------------------------------------------- + + def update_build_dimensions(self, param, value): + self.build_dimensions_list = parse_build_dimensions(value) + self.p.analyzer.home_pos = get_home_pos(self.build_dimensions_list) + + def update_tcp_streaming_mode(self, param, value): + self.p.tcp_streaming_mode = self.settings.tcp_streaming_mode + + def update_rpc_server(self, param, value): + if value: + if self.rpc_server is None: + self.rpc_server = ProntRPC(self) + else: + if self.rpc_server is not None: + self.rpc_server.shutdown() + self.rpc_server = None + + # -------------------------------------------------------------- + # Command line options handling + # -------------------------------------------------------------- + + def add_cmdline_arguments(self, parser): + parser.add_argument('-v', '--verbose', help = _("increase verbosity"), action = "store_true") + parser.add_argument('-c', '--conf', '--config', help = _("load this file on startup instead of .pronsolerc ; you may chain config files, if so settings auto-save will use the last specified file"), action = "append", default = []) + parser.add_argument('-e', '--execute', help = _("executes command after configuration/.pronsolerc is loaded ; macros/settings from these commands are not autosaved"), action = "append", default = []) + parser.add_argument('filename', nargs='?', help = _("file to load")) + + def process_cmdline_arguments(self, args): + if args.verbose: + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + for config in args.conf: + self.load_rc(config) + if not self.rc_loaded: + self.load_default_rc() + self.processing_args = True + for command in args.execute: + self.onecmd(command) + self.processing_args = False + self.update_rpc_server(None, self.settings.rpc_server) + if args.filename: + filename = args.filename.decode(locale.getpreferredencoding()) + self.cmdline_filename_callback(filename) + + def cmdline_filename_callback(self, filename): + self.do_load(filename) + + def parse_cmdline(self, args): + parser = argparse.ArgumentParser(description = 'Printrun 3D printer interface') + self.add_cmdline_arguments(parser) + args = [arg for arg in args if not arg.startswith("-psn")] + args = parser.parse_args(args = args) + self.process_cmdline_arguments(args) + setup_logging(sys.stdout, self.settings.log_path, True) + + # -------------------------------------------------------------- + # Printer connection handling + # -------------------------------------------------------------- + + def connect_to_printer(self, port, baud, dtr): + try: + self.p.connect(port, baud, dtr) + except SerialException as e: + # Currently, there is no errno, but it should be there in the future + if e.errno == 2: + self.logError(_("Error: You are trying to connect to a non-existing port.")) + elif e.errno == 8: + self.logError(_("Error: You don't have permission to open %s.") % port) + self.logError(_("You might need to add yourself to the dialout group.")) + else: + self.logError(traceback.format_exc()) + # Kill the scope anyway + return False + except OSError as e: + if e.errno == 2: + self.logError(_("Error: You are trying to connect to a non-existing port.")) + else: + self.logError(traceback.format_exc()) + return False + self.statuscheck = True + self.status_thread = threading.Thread(target = self.statuschecker) + self.status_thread.start() + return True + + def do_connect(self, l): + a = l.split() + p = self.scanserial() + port = self.settings.port + if (port == "" or port not in p) and len(p) > 0: + port = p[0] + baud = self.settings.baudrate or 115200 + if len(a) > 0: + port = a[0] + if len(a) > 1: + try: + baud = int(a[1]) + except: + self.log("Bad baud value '" + a[1] + "' ignored") + if len(p) == 0 and not port: + self.log("No serial ports detected - please specify a port") + return + if len(a) == 0: + self.log("No port specified - connecting to %s at %dbps" % (port, baud)) + if port != self.settings.port: + self.settings.port = port + self.save_in_rc("set port", "set port %s" % port) + if baud != self.settings.baudrate: + self.settings.baudrate = baud + self.save_in_rc("set baudrate", "set baudrate %d" % baud) + self.connect_to_printer(port, baud, self.settings.dtr) + + def help_connect(self): + self.log("Connect to printer") + self.log("connect ") + self.log("If port and baudrate are not specified, connects to first detected port at 115200bps") + ports = self.scanserial() + if ports: + self.log("Available ports: ", " ".join(ports)) + else: + self.log("No serial ports were automatically found.") + + def complete_connect(self, text, line, begidx, endidx): + if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): + return [i for i in self.scanserial() if i.startswith(text)] + elif len(line.split()) == 3 or (len(line.split()) == 2 and line[-1] == " "): + return [i for i in ["2400", "9600", "19200", "38400", "57600", "115200"] if i.startswith(text)] + else: + return [] + + def scanserial(self): + """scan for available ports. return a list of device names.""" + baselist = [] + if os.name == "nt": + try: + key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM") + i = 0 + while(1): + baselist += [_winreg.EnumValue(key, i)[1]] + i += 1 + except: + pass + + for g in ['/dev/ttyUSB*', '/dev/ttyACM*', "/dev/tty.*", "/dev/cu.*", "/dev/rfcomm*"]: + baselist += glob.glob(g) + return filter(self._bluetoothSerialFilter, baselist) + + def _bluetoothSerialFilter(self, serial): + return not ("Bluetooth" in serial or "FireFly" in serial) + + def online(self): + self.log("\rPrinter is now online") + self.write_prompt() + + def do_disconnect(self, l): + self.p.disconnect() + + def help_disconnect(self): + self.log("Disconnects from the printer") + + def do_block_until_online(self, l): + while not self.p.online: + time.sleep(0.1) + + def help_block_until_online(self, l): + self.log("Blocks until printer is online") + self.log("Warning: if something goes wrong, this can block pronsole forever") + + # -------------------------------------------------------------- + # Printer status monitoring + # -------------------------------------------------------------- + + def statuschecker_inner(self, do_monitoring = True): + if self.p.online: + if self.p.writefailures >= 4: + self.logError(_("Disconnecting after 4 failed writes.")) + self.status_thread = None + self.disconnect() + return + if do_monitoring: + if self.sdprinting and not self.paused: + self.p.send_now("M27") + if self.m105_waitcycles % 10 == 0: + self.p.send_now("M105") + self.m105_waitcycles += 1 + cur_time = time.time() + wait_time = 0 + while time.time() < cur_time + self.monitor_interval - 0.25: + if not self.statuscheck: + break + time.sleep(0.25) + # Safeguard: if system time changes and goes back in the past, + # we could get stuck almost forever + wait_time += 0.25 + if wait_time > self.monitor_interval - 0.25: + break + # Always sleep at least a bit, if something goes wrong with the + # system time we'll avoid freezing the whole app this way + time.sleep(0.25) + + def statuschecker(self): + while self.statuscheck: + self.statuschecker_inner() + + # -------------------------------------------------------------- + # File loading handling + # -------------------------------------------------------------- + + def do_load(self, filename): + self._do_load(filename) + + def _do_load(self, filename): + if not filename: + self.logError("No file name given.") + return + self.log(_("Loading file: %s") % filename) + if not os.path.exists(filename): + self.logError("File not found!") + return + self.load_gcode(filename) + self.log(_("Loaded %s, %d lines.") % (filename, len(self.fgcode))) + self.log(_("Estimated duration: %d layers, %s") % self.fgcode.estimate_duration()) + + def load_gcode(self, filename, layer_callback = None, gcode = None): + if gcode is None: + self.fgcode = gcoder.LightGCode(deferred = True) + else: + self.fgcode = gcode + self.fgcode.prepare(open(filename, "rU"), + get_home_pos(self.build_dimensions_list), + layer_callback = layer_callback) + self.fgcode.estimate_duration() + self.filename = filename + + def complete_load(self, text, line, begidx, endidx): + s = line.split() + if len(s) > 2: + return [] + if (len(s) == 1 and line[-1] == " ") or (len(s) == 2 and line[-1] != " "): + if len(s) > 1: + return [i[len(s[1]) - len(text):] for i in glob.glob(s[1] + "*/") + glob.glob(s[1] + "*.g*")] + else: + return glob.glob("*/") + glob.glob("*.g*") + + def help_load(self): + self.log("Loads a gcode file (with tab-completion)") + + def do_slice(self, l): + l = l.split() + if len(l) == 0: + self.logError(_("No file name given.")) + return + settings = 0 + if l[0] == "set": + settings = 1 + else: + self.log(_("Slicing file: %s") % l[0]) + if not(os.path.exists(l[0])): + self.logError(_("File not found!")) + return + try: + if settings: + command = self.settings.sliceoptscommand + self.log(_("Entering slicer settings: %s") % command) + run_command(command, blocking = True) + else: + command = self.settings.slicecommand + stl_name = l[0] + gcode_name = stl_name.replace(".stl", "_export.gcode").replace(".STL", "_export.gcode") + run_command(command, + {"$s": stl_name, + "$o": gcode_name}, + blocking = True) + self.log(_("Loading sliced file.")) + self.do_load(l[0].replace(".stl", "_export.gcode")) + except Exception, e: + self.logError(_("Slicing failed: %s") % e) + + def complete_slice(self, text, line, begidx, endidx): + s = line.split() + if len(s) > 2: + return [] + if (len(s) == 1 and line[-1] == " ") or (len(s) == 2 and line[-1] != " "): + if len(s) > 1: + return [i[len(s[1]) - len(text):] for i in glob.glob(s[1] + "*/") + glob.glob(s[1] + "*.stl")] + else: + return glob.glob("*/") + glob.glob("*.stl") + + def help_slice(self): + self.log(_("Creates a gcode file from an stl model using the slicer (with tab-completion)")) + self.log(_("slice filename.stl - create gcode file")) + self.log(_("slice filename.stl view - create gcode file and view using skeiniso (if using skeinforge)")) + self.log(_("slice set - adjust slicer settings")) + + # -------------------------------------------------------------- + # Print/upload handling + # -------------------------------------------------------------- + + def do_upload(self, l): + names = l.split() + if len(names) == 2: + filename = names[0] + targetname = names[1] + else: + self.logError(_("Please enter target name in 8.3 format.")) + return + if not self.p.online: + self.logError(_("Not connected to printer.")) + return + self._do_load(filename) + self.log(_("Uploading as %s") % targetname) + self.log(_("Uploading %s") % self.filename) + self.p.send_now("M28 " + targetname) + self.log(_("Press Ctrl-C to interrupt upload.")) + self.p.startprint(self.fgcode) + try: + sys.stdout.write(_("Progress: ") + "00.0%") + sys.stdout.flush() + while self.p.printing: + time.sleep(0.5) + sys.stdout.write("\b\b\b\b\b%04.1f%%" % (100 * float(self.p.queueindex) / len(self.p.mainqueue),)) + sys.stdout.flush() + self.p.send_now("M29 " + targetname) + time.sleep(0.2) + self.p.clear = True + self._do_ls(False) + self.log("\b\b\b\b\b100%.") + self.log(_("Upload completed. %s should now be on the card.") % targetname) + return + except (KeyboardInterrupt, Exception) as e: + if isinstance(e, KeyboardInterrupt): + self.logError(_("...interrupted!")) + else: + self.logError(_("Something wrong happened while uploading:") + + "\n" + traceback.format_exc()) + self.p.pause() + self.p.send_now("M29 " + targetname) + time.sleep(0.2) + self.p.cancelprint() + self.logError(_("A partial file named %s may have been written to the sd card.") % targetname) + + def complete_upload(self, text, line, begidx, endidx): + s = line.split() + if len(s) > 2: + return [] + if (len(s) == 1 and line[-1] == " ") or (len(s) == 2 and line[-1] != " "): + if len(s) > 1: + return [i[len(s[1]) - len(text):] for i in glob.glob(s[1] + "*/") + glob.glob(s[1] + "*.g*")] + else: + return glob.glob("*/") + glob.glob("*.g*") + + def help_upload(self): + self.log("Uploads a gcode file to the sd card") + + def help_print(self): + if not self.fgcode: + self.log(_("Send a loaded gcode file to the printer. Load a file with the load command first.")) + else: + self.log(_("Send a loaded gcode file to the printer. You have %s loaded right now.") % self.filename) + + def do_print(self, l): + if not self.fgcode: + self.logError(_("No file loaded. Please use load first.")) + return + if not self.p.online: + self.logError(_("Not connected to printer.")) + return + self.log(_("Printing %s") % self.filename) + self.log(_("You can monitor the print with the monitor command.")) + self.sdprinting = False + self.p.startprint(self.fgcode) + + def do_pause(self, l): + if self.sdprinting: + self.p.send_now("M25") + else: + if not self.p.printing: + self.logError(_("Not printing, cannot pause.")) + return + self.p.pause() + self.paused = True + + def help_pause(self): + self.log(_("Pauses a running print")) + + def pause(self, event = None): + return self.do_pause(None) + + def do_resume(self, l): + if not self.paused: + self.logError(_("Not paused, unable to resume. Start a print first.")) + return + self.paused = False + if self.sdprinting: + self.p.send_now("M24") + return + else: + self.p.resume() + + def help_resume(self): + self.log(_("Resumes a paused print.")) + + def listfiles(self, line): + if "Begin file list" in line: + self.sdlisting = 1 + elif "End file list" in line: + self.sdlisting = 0 + self.recvlisteners.remove(self.listfiles) + if self.sdlisting_echo: + self.log(_("Files on SD card:")) + self.log("\n".join(self.sdfiles)) + elif self.sdlisting: + self.sdfiles.append(line.strip().lower()) + + def _do_ls(self, echo): + # FIXME: this was 2, but I think it should rather be 0 as in do_upload + self.sdlisting = 0 + self.sdlisting_echo = echo + self.sdfiles = [] + self.recvlisteners.append(self.listfiles) + self.p.send_now("M20") + + def do_ls(self, l): + if not self.p.online: + self.logError(_("Printer is not online. Please connect to it first.")) + return + self._do_ls(True) + + def help_ls(self): + self.log(_("Lists files on the SD card")) + + def waitforsdresponse(self, l): + if "file.open failed" in l: + self.logError(_("Opening file failed.")) + self.recvlisteners.remove(self.waitforsdresponse) + return + if "File opened" in l: + self.log(l) + if "File selected" in l: + self.log(_("Starting print")) + self.p.send_now("M24") + self.sdprinting = True + # self.recvlisteners.remove(self.waitforsdresponse) + return + if "Done printing file" in l: + self.log(l) + self.sdprinting = False + self.recvlisteners.remove(self.waitforsdresponse) + return + if "SD printing byte" in l: + # M27 handler + try: + resp = l.split() + vals = resp[-1].split("/") + self.percentdone = 100.0 * int(vals[0]) / int(vals[1]) + except: + pass + + def do_reset(self, l): + self.p.reset() + + def help_reset(self): + self.log(_("Resets the printer.")) + + def do_sdprint(self, l): + if not self.p.online: + self.log(_("Printer is not online. Please connect to it first.")) + return + self._do_ls(False) + while self.listfiles in self.recvlisteners: + time.sleep(0.1) + if l.lower() not in self.sdfiles: + self.log(_("File is not present on card. Please upload it first.")) + return + self.recvlisteners.append(self.waitforsdresponse) + self.p.send_now("M23 " + l.lower()) + self.log(_("Printing file: %s from SD card.") % l.lower()) + self.log(_("Requesting SD print...")) + time.sleep(1) + + def help_sdprint(self): + self.log(_("Print a file from the SD card. Tab completes with available file names.")) + self.log(_("sdprint filename.g")) + + def complete_sdprint(self, text, line, begidx, endidx): + if not self.sdfiles and self.p.online: + self._do_ls(False) + while self.listfiles in self.recvlisteners: + time.sleep(0.1) + if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): + return [i for i in self.sdfiles if i.startswith(text)] + + # -------------------------------------------------------------- + # Printcore callbacks + # -------------------------------------------------------------- + + def startcb(self, resuming = False): + self.starttime = time.time() + if resuming: + self.log(_("Print resumed at: %s") % format_time(self.starttime)) + else: + self.log(_("Print started at: %s") % format_time(self.starttime)) + if not self.sdprinting: + self.compute_eta = RemainingTimeEstimator(self.fgcode) + else: + self.compute_eta = None + + if self.settings.start_command: + output = get_command_output(self.settings.start_command, + {"$s": str(self.filename), + "$t": format_time(time.time())}) + if output: + self.log("Start command output:") + self.log(output.rstrip()) + try: + powerset_print_start(reason = "Preventing sleep during print") + except: + self.logError(_("Failed to set power settings:") + + "\n" + traceback.format_exc()) + + def endcb(self): + try: + powerset_print_stop() + except: + self.logError(_("Failed to set power settings:") + + "\n" + traceback.format_exc()) + if self.p.queueindex == 0: + print_duration = int(time.time() - self.starttime + self.extra_print_time) + self.log(_("Print ended at: %(end_time)s and took %(duration)s") % {"end_time": format_time(time.time()), + "duration": format_duration(print_duration)}) + + # Update total filament length used + if self.fgcode is not None: + new_total = self.settings.total_filament_used + self.fgcode.filament_length + self.set("total_filament_used", new_total) + + if not self.settings.final_command: + return + output = get_command_output(self.settings.final_command, + {"$s": str(self.filename), + "$t": format_duration(print_duration)}) + if output: + self.log("Final command output:") + self.log(output.rstrip()) + + def recvcb_report(self, l): + isreport = REPORT_NONE + if "ok C:" in l or "Count" in l \ + or ("X:" in l and len(gcoder.m114_exp.findall(l)) == 6): + self.posreport = l + isreport = REPORT_POS + if self.userm114 > 0: + self.userm114 -= 1 + isreport |= REPORT_MANUAL + if "ok T:" in l or tempreading_exp.findall(l): + self.tempreadings = l + isreport = REPORT_TEMP + if self.userm105 > 0: + self.userm105 -= 1 + isreport |= REPORT_MANUAL + else: + self.m105_waitcycles = 0 + return isreport + + def recvcb_actions(self, l): + if l.startswith("!!"): + self.do_pause(None) + msg = l.split(" ", 1) + if len(msg) > 1 and self.silent is False: self.logError(msg[1].ljust(15)) + sys.stdout.write(self.promptf()) + sys.stdout.flush() + return True + elif l.startswith("//"): + command = l.split(" ", 1) + if len(command) > 1: + command = command[1] + self.log(_("Received command %s") % command) + command = command.split(":") + if len(command) == 2 and command[0] == "action": + command = command[1] + if command == "pause": + self.do_pause(None) + sys.stdout.write(self.promptf()) + sys.stdout.flush() + return True + elif command == "resume": + self.do_resume(None) + sys.stdout.write(self.promptf()) + sys.stdout.flush() + return True + elif command == "disconnect": + self.do_disconnect(None) + sys.stdout.write(self.promptf()) + sys.stdout.flush() + return True + return False + + def recvcb(self, l): + l = l.rstrip() + for listener in self.recvlisteners: + listener(l) + if not self.recvcb_actions(l): + report_type = self.recvcb_report(l) + if report_type & REPORT_TEMP: + self.status.update_tempreading(l) + if l != "ok" and not self.sdlisting \ + and not self.monitoring and (report_type == REPORT_NONE or report_type & REPORT_MANUAL): + if l[:5] == "echo:": + l = l[5:].lstrip() + if self.silent is False: self.log("\r" + l.ljust(15)) + sys.stdout.write(self.promptf()) + sys.stdout.flush() + + def layer_change_cb(self, newlayer): + layerz = self.fgcode.all_layers[newlayer].z + if layerz is not None: + self.curlayer = layerz + if self.compute_eta: + secondselapsed = int(time.time() - self.starttime + self.extra_print_time) + self.compute_eta.update_layer(newlayer, secondselapsed) + + def get_eta(self): + if self.sdprinting or self.uploading: + if self.uploading: + fractioncomplete = float(self.p.queueindex) / len(self.p.mainqueue) + else: + fractioncomplete = float(self.percentdone / 100.0) + secondselapsed = int(time.time() - self.starttime + self.extra_print_time) + # Prevent division by zero + secondsestimate = secondselapsed / max(fractioncomplete, 0.000001) + secondsremain = secondsestimate - secondselapsed + progress = fractioncomplete + elif self.compute_eta is not None: + secondselapsed = int(time.time() - self.starttime + self.extra_print_time) + secondsremain, secondsestimate = self.compute_eta(self.p.queueindex, secondselapsed) + progress = self.p.queueindex + else: + secondsremain, secondsestimate, progress = 1, 1, 0 + return secondsremain, secondsestimate, progress + + def do_eta(self, l): + if not self.p.printing: + self.logError(_("Printer is not currently printing. No ETA available.")) + else: + secondsremain, secondsestimate, progress = self.get_eta() + eta = _("Est: %s of %s remaining") % (format_duration(secondsremain), + format_duration(secondsestimate)) + self.log(eta.strip()) + + def help_eta(self): + self.log(_("Displays estimated remaining print time.")) + + # -------------------------------------------------------------- + # Temperature handling + # -------------------------------------------------------------- + + def set_temp_preset(self, key, value): + if not key.startswith("bed"): + self.temps["pla"] = str(self.settings.temperature_pla) + self.temps["abs"] = str(self.settings.temperature_abs) + self.log("Hotend temperature presets updated, pla:%s, abs:%s" % (self.temps["pla"], self.temps["abs"])) + else: + self.bedtemps["pla"] = str(self.settings.bedtemp_pla) + self.bedtemps["abs"] = str(self.settings.bedtemp_abs) + self.log("Bed temperature presets updated, pla:%s, abs:%s" % (self.bedtemps["pla"], self.bedtemps["abs"])) + + def tempcb(self, l): + if "T:" in l: + self.log(l.strip().replace("T", "Hotend").replace("B", "Bed").replace("ok ", "")) + + def do_gettemp(self, l): + if "dynamic" in l: + self.dynamic_temp = True + if self.p.online: + self.p.send_now("M105") + time.sleep(0.75) + if not self.status.bed_enabled: + self.log(_("Hotend: %s/%s") % (self.status.extruder_temp, self.status.extruder_temp_target)) + else: + self.log(_("Hotend: %s/%s") % (self.status.extruder_temp, self.status.extruder_temp_target)) + self.log(_("Bed: %s/%s") % (self.status.bed_temp, self.status.bed_temp_target)) + + def help_gettemp(self): + self.log(_("Read the extruder and bed temperature.")) + + def do_settemp(self, l): + l = l.lower().replace(", ", ".") + for i in self.temps.keys(): + l = l.replace(i, self.temps[i]) + try: + f = float(l) + except: + self.logError(_("You must enter a temperature.")) + return + + if f >= 0: + if f > 250: + self.log(_("%s is a high temperature to set your extruder to. Are you sure you want to do that?") % f) + if not self.confirm(): + return + if self.p.online: + self.p.send_now("M104 S" + l) + self.log(_("Setting hotend temperature to %s degrees Celsius.") % f) + else: + self.logError(_("Printer is not online.")) + else: + self.logError(_("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")) + + def help_settemp(self): + self.log(_("Sets the hotend temperature to the value entered.")) + self.log(_("Enter either a temperature in celsius or one of the following keywords")) + self.log(", ".join([i + "(" + self.temps[i] + ")" for i in self.temps.keys()])) + + def complete_settemp(self, text, line, begidx, endidx): + if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): + return [i for i in self.temps.keys() if i.startswith(text)] + + def do_bedtemp(self, l): + f = None + try: + l = l.lower().replace(", ", ".") + for i in self.bedtemps.keys(): + l = l.replace(i, self.bedtemps[i]) + f = float(l) + except: + self.logError(_("You must enter a temperature.")) + if f is not None and f >= 0: + if self.p.online: + self.p.send_now("M140 S" + l) + self.log(_("Setting bed temperature to %s degrees Celsius.") % f) + else: + self.logError(_("Printer is not online.")) + else: + self.logError(_("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")) + + def help_bedtemp(self): + self.log(_("Sets the bed temperature to the value entered.")) + self.log(_("Enter either a temperature in celsius or one of the following keywords")) + self.log(", ".join([i + "(" + self.bedtemps[i] + ")" for i in self.bedtemps.keys()])) + + def complete_bedtemp(self, text, line, begidx, endidx): + if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): + return [i for i in self.bedtemps.keys() if i.startswith(text)] + + def do_monitor(self, l): + interval = 5 + if not self.p.online: + self.logError(_("Printer is not online. Please connect to it first.")) + return + if not (self.p.printing or self.sdprinting): + self.logError(_("Printer is not printing. Please print something before monitoring.")) + return + self.log(_("Monitoring printer, use ^C to interrupt.")) + if len(l): + try: + interval = float(l) + except: + self.logError(_("Invalid period given.")) + self.log(_("Updating values every %f seconds.") % (interval,)) + self.monitoring = 1 + prev_msg_len = 0 + try: + while True: + self.p.send_now("M105") + if self.sdprinting: + self.p.send_now("M27") + time.sleep(interval) + if self.p.printing: + preface = _("Print progress: ") + progress = 100 * float(self.p.queueindex) / len(self.p.mainqueue) + elif self.sdprinting: + preface = _("SD print progress: ") + progress = self.percentdone + prev_msg = preface + "%.1f%%" % progress + if self.silent is False: + sys.stdout.write("\r" + prev_msg.ljust(prev_msg_len)) + sys.stdout.flush() + prev_msg_len = len(prev_msg) + except KeyboardInterrupt: + if self.silent is False: self.log(_("Done monitoring.")) + self.monitoring = 0 + + def help_monitor(self): + self.log(_("Monitor a machine's temperatures and an SD print's status.")) + self.log(_("monitor - Reports temperature and SD print status (if SD printing) every 5 seconds")) + self.log(_("monitor 2 - Reports temperature and SD print status (if SD printing) every 2 seconds")) + + # -------------------------------------------------------------- + # Manual printer controls + # -------------------------------------------------------------- + + def do_tool(self, l): + tool = None + try: + tool = int(l.lower().strip()) + except: + self.logError(_("You must specify the tool index as an integer.")) + if tool is not None and tool >= 0: + if self.p.online: + self.p.send_now("T%d" % tool) + self.log(_("Using tool %d.") % tool) + else: + self.logError(_("Printer is not online.")) + else: + self.logError(_("You cannot set negative tool numbers.")) + + def help_tool(self): + self.log(_("Switches to the specified tool (e.g. doing tool 1 will emit a T1 G-Code).")) + + def do_move(self, l): + if len(l.split()) < 2: + self.logError(_("No move specified.")) + return + if self.p.printing: + self.logError(_("Printer is currently printing. Please pause the print before you issue manual commands.")) + return + if not self.p.online: + self.logError(_("Printer is not online. Unable to move.")) + return + l = l.split() + if l[0].lower() == "x": + feed = self.settings.xy_feedrate + axis = "X" + elif l[0].lower() == "y": + feed = self.settings.xy_feedrate + axis = "Y" + elif l[0].lower() == "z": + feed = self.settings.z_feedrate + axis = "Z" + elif l[0].lower() == "e": + feed = self.settings.e_feedrate + axis = "E" + else: + self.logError(_("Unknown axis.")) + return + try: + float(l[1]) # check if distance can be a float + except: + self.logError(_("Invalid distance")) + return + try: + feed = int(l[2]) + except: + pass + self.p.send_now("G91") + self.p.send_now("G0 " + axis + str(l[1]) + " F" + str(feed)) + self.p.send_now("G90") + + def help_move(self): + self.log(_("Move an axis. Specify the name of the axis and the amount. ")) + self.log(_("move X 10 will move the X axis forward by 10mm at %s mm/min (default XY speed)") % self.settings.xy_feedrate) + self.log(_("move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min")) + self.log(_("move Z -1 will move the Z axis down by 1mm at %s mm/min (default Z speed)") % self.settings.z_feedrate) + self.log(_("Common amounts are in the tabcomplete list.")) + + def complete_move(self, text, line, begidx, endidx): + if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): + return [i for i in ["X ", "Y ", "Z ", "E "] if i.lower().startswith(text)] + elif len(line.split()) == 3 or (len(line.split()) == 2 and line[-1] == " "): + base = line.split()[-1] + rlen = 0 + if base.startswith("-"): + rlen = 1 + if line[-1] == " ": + base = "" + return [i[rlen:] for i in ["-100", "-10", "-1", "-0.1", "100", "10", "1", "0.1", "-50", "-5", "-0.5", "50", "5", "0.5", "-200", "-20", "-2", "-0.2", "200", "20", "2", "0.2"] if i.startswith(base)] + else: + return [] + + def do_extrude(self, l, override = None, overridefeed = 300): + length = self.settings.default_extrusion # default extrusion length + feed = self.settings.e_feedrate # default speed + if not self.p.online: + self.logError("Printer is not online. Unable to extrude.") + return + if self.p.printing: + self.logError(_("Printer is currently printing. Please pause the print before you issue manual commands.")) + return + ls = l.split() + if len(ls): + try: + length = float(ls[0]) + except: + self.logError(_("Invalid length given.")) + if len(ls) > 1: + try: + feed = int(ls[1]) + except: + self.logError(_("Invalid speed given.")) + if override is not None: + length = override + feed = overridefeed + self.do_extrude_final(length, feed) + + def do_extrude_final(self, length, feed): + if length > 0: + self.log(_("Extruding %fmm of filament.") % (length,)) + elif length < 0: + self.log(_("Reversing %fmm of filament.") % (-length,)) + else: + self.log(_("Length is 0, not doing anything.")) + self.p.send_now("G91") + self.p.send_now("G1 E" + str(length) + " F" + str(feed)) + self.p.send_now("G90") + + def help_extrude(self): + self.log(_("Extrudes a length of filament, 5mm by default, or the number of mm given as a parameter")) + self.log(_("extrude - extrudes 5mm of filament at 300mm/min (5mm/s)")) + self.log(_("extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)")) + self.log(_("extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)")) + self.log(_("extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)")) + + def do_reverse(self, l): + length = self.settings.default_extrusion # default extrusion length + feed = self.settings.e_feedrate # default speed + if not self.p.online: + self.logError(_("Printer is not online. Unable to reverse.")) + return + if self.p.printing: + self.logError(_("Printer is currently printing. Please pause the print before you issue manual commands.")) + return + ls = l.split() + if len(ls): + try: + length = float(ls[0]) + except: + self.logError(_("Invalid length given.")) + if len(ls) > 1: + try: + feed = int(ls[1]) + except: + self.logError(_("Invalid speed given.")) + self.do_extrude("", -length, feed) + + def help_reverse(self): + self.log(_("Reverses the extruder, 5mm by default, or the number of mm given as a parameter")) + self.log(_("reverse - reverses 5mm of filament at 300mm/min (5mm/s)")) + self.log(_("reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)")) + self.log(_("reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)")) + self.log(_("reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)")) + + def do_home(self, l): + if not self.p.online: + self.logError(_("Printer is not online. Unable to move.")) + return + if self.p.printing: + self.logError(_("Printer is currently printing. Please pause the print before you issue manual commands.")) + return + if "x" in l.lower(): + self.p.send_now("G28 X0") + if "y" in l.lower(): + self.p.send_now("G28 Y0") + if "z" in l.lower(): + self.p.send_now("G28 Z0") + if "e" in l.lower(): + self.p.send_now("G92 E0") + if not len(l): + self.p.send_now("G28") + self.p.send_now("G92 E0") + + def help_home(self): + self.log(_("Homes the printer")) + self.log(_("home - homes all axes and zeroes the extruder(Using G28 and G92)")) + self.log(_("home xy - homes x and y axes (Using G28)")) + self.log(_("home z - homes z axis only (Using G28)")) + self.log(_("home e - set extruder position to zero (Using G92)")) + self.log(_("home xyze - homes all axes and zeroes the extruder (Using G28 and G92)")) + + def do_off(self, l): + self.off() + + def off(self, ignore = None): + if self.p.online: + if self.p.printing: self.pause(None) + self.log(_("; Motors off")) + self.onecmd("M84") + self.log(_("; Extruder off")) + self.onecmd("M104 S0") + self.log(_("; Heatbed off")) + self.onecmd("M140 S0") + self.log(_("; Fan off")) + self.onecmd("M107") + self.log(_("; Power supply off")) + self.onecmd("M81") + else: + self.logError(_("Printer is not online. Unable to turn it off.")) + + def help_off(self): + self.log(_("Turns off everything on the printer")) + + # -------------------------------------------------------------- + # Host commands handling + # -------------------------------------------------------------- + + def process_host_command(self, command): + """Override host command handling""" + command = command.lstrip() + if command.startswith(";@"): + command = command[2:] + self.log(_("G-Code calling host command \"%s\"") % command) + self.onecmd(command) + + def do_run_script(self, l): + p = run_command(l, {"$s": str(self.filename)}, stdout = subprocess.PIPE) + for line in p.stdout.readlines(): + self.log("<< " + line.strip()) + + def help_run_script(self): + self.log(_("Runs a custom script. Current gcode filename can be given using $s token.")) + + def do_run_gcode_script(self, l): + p = run_command(l, {"$s": str(self.filename)}, stdout = subprocess.PIPE) + for line in p.stdout.readlines(): + self.onecmd(line.strip()) + + def help_run_gcode_script(self): + self.log(_("Runs a custom script which output gcode which will in turn be executed. Current gcode filename can be given using $s token.")) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/pronterface.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/pronterface.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,2223 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import os +import Queue +import sys +import time +import threading +import traceback +import cStringIO as StringIO +import subprocess +import glob +import logging + +try: import simplejson as json +except ImportError: import json + +from . import pronsole +from . import printcore + +from .utils import install_locale, setup_logging, dosify, \ + iconfile, configfile, format_time, format_duration, \ + hexcolor_to_float, parse_temperature_report, \ + prepare_command, check_rgb_color, check_rgba_color +install_locale('pronterface') + +try: + import wx +except: + logging.error(_("WX is not installed. This program requires WX to run.")) + raise + +from .gui.widgets import SpecialButton, MacroEditor, PronterOptions, ButtonEdit + +winsize = (800, 500) +layerindex = 0 +if os.name == "nt": + winsize = (800, 530) + +pronterface_quitting = False + +class PronterfaceQuitException(Exception): + pass + +from .gui import MainWindow +from .settings import wxSetting, HiddenSetting, StringSetting, SpinSetting, \ + FloatSpinSetting, BooleanSetting, StaticTextSetting +from printrun import gcoder +from .pronsole import REPORT_NONE, REPORT_POS, REPORT_TEMP, REPORT_MANUAL + +class ConsoleOutputHandler(object): + """Handle console output. All messages go through the logging submodule. We setup a logging handler to get logged messages and write them to both stdout (unless a log file path is specified, in which case we add another logging handler to write to this file) and the log panel. + We also redirect stdout and stderr to ourself to catch print messages and al.""" + + def __init__(self, target, log_path): + self.stdout = sys.stdout + self.stderr = sys.stderr + sys.stdout = self + sys.stderr = self + if log_path: + self.print_on_stdout = False + setup_logging(self, log_path, reset_handlers = True) + self.target = target + else: + self.print_on_stdout = True + setup_logging(sys.stdout) + self.target = target + + def __del__(self): + sys.stdout = self.stdout + sys.stderr = self.stderr + + def write(self, data): + try: + self.target(data) + except: + pass + if self.print_on_stdout: + try: + data = data.encode("utf-8") + except: + pass + self.stdout.write(data) + + def flush(self): + if self.stdout: + self.stdout.flush() + +class ComboSetting(wxSetting): + + def __init__(self, name, default, choices, label = None, help = None, group = None): + super(ComboSetting, self).__init__(name, default, label, help, group) + self.choices = choices + + def get_specific_widget(self, parent): + import wx + self.widget = wx.ComboBox(parent, -1, str(self.value), choices = self.choices, style = wx.CB_DROPDOWN) + return self.widget + +class PronterWindow(MainWindow, pronsole.pronsole): + + _fgcode = None + + def _get_fgcode(self): + return self._fgcode + + def _set_fgcode(self, value): + self._fgcode = value + self.excluder = None + self.excluder_e = None + self.excluder_z_abs = None + self.excluder_z_rel = None + fgcode = property(_get_fgcode, _set_fgcode) + + def _get_display_graph(self): + return self.settings.tempgraph + display_graph = property(_get_display_graph) + + def _get_display_gauges(self): + return self.settings.tempgauges + display_gauges = property(_get_display_gauges) + + def __init__(self, app, filename = None, size = winsize): + pronsole.pronsole.__init__(self) + self.app = app + self.window_ready = False + self.ui_ready = False + self._add_settings(size) + + for field in dir(self.settings): + if field.startswith("_gcview_color_"): + cleanname = field[1:] + color = hexcolor_to_float(getattr(self.settings, cleanname), 4) + setattr(self, cleanname, list(color)) + + self.pauseScript = None #"pause.gcode" + self.endScript = None #"end.gcode" + + self.filename = filename + + self.capture_skip = {} + self.capture_skip_newline = False + self.fgcode = None + self.excluder = None + self.slicep = None + self.current_pos = [0, 0, 0] + self.paused = False + self.uploading = False + self.sentglines = Queue.Queue(0) + self.cpbuttons = { + "motorsoff": SpecialButton(_("Motors off"), ("M84"), (250, 250, 250), _("Switch all motors off")), + "extrude": SpecialButton(_("Extrude"), ("pront_extrude"), (225, 200, 200), _("Advance extruder by set length")), + "reverse": SpecialButton(_("Reverse"), ("pront_reverse"), (225, 200, 200), _("Reverse extruder by set length")), + } + self.custombuttons = [] + self.btndict = {} + self.filehistory = None + self.autoconnect = False + self.parse_cmdline(sys.argv[1:]) + + # FIXME: We need to initialize the main window after loading the + # configs to restore the size, but this might have some unforeseen + # consequences. + # -- Okai, it seems it breaks things like update_gviz_params >< + os.putenv("UBUNTU_MENUPROXY", "0") + size = (self.settings.last_window_width, self.settings.last_window_height) + MainWindow.__init__(self, None, title = _("Pronterface"), size = size) + if self.settings.last_window_maximized: + self.Maximize() + self.SetIcon(wx.Icon(iconfile("pronterface.png"), wx.BITMAP_TYPE_PNG)) + self.Bind(wx.EVT_SIZE, self.on_resize) + self.Bind(wx.EVT_MAXIMIZE, self.on_maximize) + self.window_ready = True + + # set feedrates in printcore for pause/resume + self.p.xy_feedrate = self.settings.xy_feedrate + self.p.z_feedrate = self.settings.z_feedrate + + self.panel.SetBackgroundColour(self.bgcolor) + customdict = {} + try: + execfile(configfile("custombtn.txt"), customdict) + if len(customdict["btns"]): + if not len(self.custombuttons): + try: + self.custombuttons = customdict["btns"] + for n in xrange(len(self.custombuttons)): + self.cbutton_save(n, self.custombuttons[n]) + os.rename("custombtn.txt", "custombtn.old") + rco = open("custombtn.txt", "w") + rco.write(_("# I moved all your custom buttons into .pronsolerc.\n# Please don't add them here any more.\n# Backup of your old buttons is in custombtn.old\n")) + rco.close() + except IOError, x: + logging.error(str(x)) + else: + logging.warning(_("Note!!! You have specified custom buttons in both custombtn.txt and .pronsolerc")) + logging.warning(_("Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt")) + + except: + pass + self.create_menu() + self.update_recent_files("recentfiles", self.settings.recentfiles) + + self.reload_ui() + # disable all printer controls until we connect to a printer + self.gui_set_disconnected() + self.statusbar = self.CreateStatusBar() + self.statusbar.SetStatusText(_("Not connected to printer.")) + + self.t = ConsoleOutputHandler(self.catchprint, self.settings.log_path) + self.stdout = sys.stdout + self.slicing = False + self.loading_gcode = False + self.loading_gcode_message = "" + self.mini = False + self.p.sendcb = self.sentcb + self.p.preprintsendcb = self.preprintsendcb + self.p.printsendcb = self.printsentcb + self.p.startcb = self.startcb + self.p.endcb = self.endcb + self.cur_button = None + self.predisconnect_mainqueue = None + self.predisconnect_queueindex = None + self.predisconnect_layer = None + self.hsetpoint = 0.0 + self.bsetpoint = 0.0 + if self.autoconnect: + self.connect() + if self.filename is not None: + self.do_load(self.filename) + if self.settings.monitor: + self.update_monitor() + + # -------------------------------------------------------------- + # Main interface handling + # -------------------------------------------------------------- + + def reset_ui(self): + MainWindow.reset_ui(self) + self.custombuttons_widgets = [] + + def reload_ui(self, *args): + if not self.window_ready: return + self.Freeze() + + # If UI is being recreated, delete current one + if self.ui_ready: + # Store log console content + logcontent = self.logbox.GetValue() + # Create a temporary panel to reparent widgets with state we want + # to retain across UI changes + temppanel = wx.Panel(self) + # TODO: add viz widgets to statefulControls + for control in self.statefulControls: + control.GetContainingSizer().Detach(control) + control.Reparent(temppanel) + self.panel.DestroyChildren() + self.gwindow.Destroy() + self.reset_ui() + + # Create UI + if self.settings.uimode in (_("Tabbed"), _("Tabbed with platers")): + self.createTabbedGui() + else: + self.createGui(self.settings.uimode == _("Compact"), + self.settings.controlsmode == "Mini") + + if hasattr(self, "splitterwindow"): + self.splitterwindow.SetSashPosition(self.settings.last_sash_position) + + def splitter_resize(event): + self.splitterwindow.UpdateSize() + self.splitterwindow.Bind(wx.EVT_SIZE, splitter_resize) + + def sash_position_changed(event): + self.set("last_sash_position", self.splitterwindow.GetSashPosition()) + self.splitterwindow.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, sash_position_changed) + + # Set gcview parameters here as they don't get set when viewers are + # created + self.update_gcview_params() + + # Finalize + if self.online: + self.gui_set_connected() + if self.ui_ready: + self.logbox.SetValue(logcontent) + temppanel.Destroy() + self.panel.Layout() + if self.fgcode: + self.start_viz_thread() + if self.settings.monitor: + self.update_monitor() + self.ui_ready = True + self.Thaw() + + def on_resize(self, event): + wx.CallAfter(self.on_resize_real) + event.Skip() + + def on_resize_real(self): + maximized = self.IsMaximized() + self.set("last_window_maximized", maximized) + if not maximized and not self.IsIconized(): + size = self.GetSize() + self.set("last_window_width", size[0]) + self.set("last_window_height", size[1]) + + def on_maximize(self, event): + self.set("last_window_maximized", self.IsMaximized()) + event.Skip() + + def on_exit(self, event): + self.Close() + + def kill(self, e): + if self.p.printing or self.p.paused: + dlg = wx.MessageDialog(self, _("Print in progress ! Are you really sure you want to quit ?"), _("Exit"), wx.YES_NO | wx.ICON_WARNING) + if dlg.ShowModal() == wx.ID_NO: + return + pronsole.pronsole.kill(self) + global pronterface_quitting + pronterface_quitting = True + self.p.recvcb = None + self.p.disconnect() + if hasattr(self, "feedrates_changed"): + self.save_in_rc("set xy_feedrate", "set xy_feedrate %d" % self.settings.xy_feedrate) + self.save_in_rc("set z_feedrate", "set z_feedrate %d" % self.settings.z_feedrate) + self.save_in_rc("set e_feedrate", "set e_feedrate %d" % self.settings.e_feedrate) + if self.settings.last_extrusion != self.settings.default_extrusion: + self.save_in_rc("set last_extrusion", "set last_extrusion %d" % self.settings.last_extrusion) + if self.excluder: + self.excluder.close_window() + wx.CallAfter(self.gwindow.Destroy) + wx.CallAfter(self.Destroy) + + def _get_bgcolor(self): + if self.settings.bgcolor != "auto": + return self.settings.bgcolor + else: + return wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWFRAME) + bgcolor = property(_get_bgcolor) + + # -------------------------------------------------------------- + # Main interface actions + # -------------------------------------------------------------- + + def do_monitor(self, l = ""): + if l.strip() == "": + self.set("monitor", not self.settings.monitor) + elif l.strip() == "off": + self.set("monitor", False) + else: + try: + self.monitor_interval = float(l) + self.set("monitor", self.monitor_interval > 0) + except: + self.log(_("Invalid period given.")) + if self.settings.monitor: + self.log(_("Monitoring printer.")) + else: + self.log(_("Done monitoring.")) + + def do_pront_extrude(self, l = ""): + feed = self.settings.e_feedrate + self.do_extrude_final(self.edist.GetValue(), feed) + + def do_pront_reverse(self, l = ""): + feed = self.settings.e_feedrate + self.do_extrude_final(- self.edist.GetValue(), feed) + + def do_settemp(self, l = ""): + try: + if l.__class__ not in (str, unicode) or not len(l): + l = str(self.htemp.GetValue().split()[0]) + l = l.lower().replace(", ", ".") + for i in self.temps.keys(): + l = l.replace(i, self.temps[i]) + f = float(l) + if f >= 0: + if self.p.online: + self.p.send_now("M104 S" + l) + self.log(_("Setting hotend temperature to %f degrees Celsius.") % f) + self.sethotendgui(f) + else: + self.logError(_("Printer is not online.")) + else: + self.logError(_("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")) + except Exception, x: + self.logError(_("You must enter a temperature. (%s)") % (repr(x),)) + + def do_bedtemp(self, l = ""): + try: + if l.__class__ not in (str, unicode) or not len(l): + l = str(self.btemp.GetValue().split()[0]) + l = l.lower().replace(", ", ".") + for i in self.bedtemps.keys(): + l = l.replace(i, self.bedtemps[i]) + f = float(l) + if f >= 0: + if self.p.online: + self.p.send_now("M140 S" + l) + self.log(_("Setting bed temperature to %f degrees Celsius.") % f) + self.setbedgui(f) + else: + self.logError(_("Printer is not online.")) + else: + self.logError(_("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")) + except Exception, x: + self.logError(_("You must enter a temperature. (%s)") % (repr(x),)) + + def do_setspeed(self, l = ""): + try: + if l.__class__ not in (str, unicode) or not len(l): + l = str(self.speed_slider.GetValue()) + else: + l = l.lower() + speed = int(l) + if self.p.online: + self.p.send_now("M220 S" + l) + self.log(_("Setting print speed factor to %d%%.") % speed) + else: + self.logError(_("Printer is not online.")) + except Exception, x: + self.logError(_("You must enter a speed. (%s)") % (repr(x),)) + + def do_setflow(self, l = ""): + try: + if l.__class__ not in (str, unicode) or not len(l): + l = str(self.flow_slider.GetValue()) + else: + l = l.lower() + flow = int(l) + if self.p.online: + self.p.send_now("M221 S" + l) + self.log(_("Setting print flow factor to %d%%.") % flow) + else: + self.logError(_("Printer is not online.")) + except Exception, x: + self.logError(_("You must enter a flow. (%s)") % (repr(x),)) + + def setbedgui(self, f): + self.bsetpoint = f + if self.display_gauges: self.bedtgauge.SetTarget(int(f)) + if self.display_graph: wx.CallAfter(self.graph.SetBedTargetTemperature, int(f)) + if f > 0: + wx.CallAfter(self.btemp.SetValue, str(f)) + self.set("last_bed_temperature", str(f)) + wx.CallAfter(self.setboff.SetBackgroundColour, None) + wx.CallAfter(self.setboff.SetForegroundColour, None) + wx.CallAfter(self.setbbtn.SetBackgroundColour, "#FFAA66") + wx.CallAfter(self.setbbtn.SetForegroundColour, "#660000") + wx.CallAfter(self.btemp.SetBackgroundColour, "#FFDABB") + else: + wx.CallAfter(self.setboff.SetBackgroundColour, "#0044CC") + wx.CallAfter(self.setboff.SetForegroundColour, "white") + wx.CallAfter(self.setbbtn.SetBackgroundColour, None) + wx.CallAfter(self.setbbtn.SetForegroundColour, None) + wx.CallAfter(self.btemp.SetBackgroundColour, "white") + wx.CallAfter(self.btemp.Refresh) + + def sethotendgui(self, f): + self.hsetpoint = f + if self.display_gauges: self.hottgauge.SetTarget(int(f)) + if self.display_graph: wx.CallAfter(self.graph.SetExtruder0TargetTemperature, int(f)) + if f > 0: + wx.CallAfter(self.htemp.SetValue, str(f)) + self.set("last_temperature", str(f)) + wx.CallAfter(self.settoff.SetBackgroundColour, None) + wx.CallAfter(self.settoff.SetForegroundColour, None) + wx.CallAfter(self.settbtn.SetBackgroundColour, "#FFAA66") + wx.CallAfter(self.settbtn.SetForegroundColour, "#660000") + wx.CallAfter(self.htemp.SetBackgroundColour, "#FFDABB") + else: + wx.CallAfter(self.settoff.SetBackgroundColour, "#0044CC") + wx.CallAfter(self.settoff.SetForegroundColour, "white") + wx.CallAfter(self.settbtn.SetBackgroundColour, None) + wx.CallAfter(self.settbtn.SetForegroundColour, None) + wx.CallAfter(self.htemp.SetBackgroundColour, "white") + wx.CallAfter(self.htemp.Refresh) + + def rescanports(self, event = None): + scanned = self.scanserial() + portslist = list(scanned) + if self.settings.port != "" and self.settings.port not in portslist: + portslist.append(self.settings.port) + self.serialport.Clear() + self.serialport.AppendItems(portslist) + if os.path.exists(self.settings.port) or self.settings.port in scanned: + self.serialport.SetValue(self.settings.port) + elif portslist: + self.serialport.SetValue(portslist[0]) + + def cbkey(self, e): + if e.GetKeyCode() == wx.WXK_UP: + if self.commandbox.histindex == len(self.commandbox.history): + self.commandbox.history.append(self.commandbox.GetValue()) # save current command + if len(self.commandbox.history): + self.commandbox.histindex = (self.commandbox.histindex - 1) % len(self.commandbox.history) + self.commandbox.SetValue(self.commandbox.history[self.commandbox.histindex]) + self.commandbox.SetSelection(0, len(self.commandbox.history[self.commandbox.histindex])) + elif e.GetKeyCode() == wx.WXK_DOWN: + if self.commandbox.histindex == len(self.commandbox.history): + self.commandbox.history.append(self.commandbox.GetValue()) # save current command + if len(self.commandbox.history): + self.commandbox.histindex = (self.commandbox.histindex + 1) % len(self.commandbox.history) + self.commandbox.SetValue(self.commandbox.history[self.commandbox.histindex]) + self.commandbox.SetSelection(0, len(self.commandbox.history[self.commandbox.histindex])) + else: + e.Skip() + + def plate(self, e): + from . import stlplater as plater + self.log(_("Plate function activated")) + plater.StlPlater(size = (800, 580), callback = self.platecb, + parent = self, + build_dimensions = self.build_dimensions_list, + circular_platform = self.settings.circular_bed, + simarrange_path = self.settings.simarrange_path, + antialias_samples = int(self.settings.antialias3dsamples)).Show() + + def plate_gcode(self, e): + from . import gcodeplater as plater + self.log(_("G-Code plate function activated")) + plater.GcodePlater(size = (800, 580), callback = self.platecb, + parent = self, + build_dimensions = self.build_dimensions_list, + circular_platform = self.settings.circular_bed, + antialias_samples = int(self.settings.antialias3dsamples)).Show() + + def platecb(self, name): + self.log(_("Plated %s") % name) + self.loadfile(None, name) + if self.settings.uimode in (_("Tabbed"), _("Tabbed with platers")): + # Switch to page 1 (Status tab) + self.notebook.SetSelection(1) + + def do_editgcode(self, e = None): + if self.filename is not None: + MacroEditor(self.filename, [line.raw for line in self.fgcode], self.doneediting, True) + + def doneediting(self, gcode): + open(self.filename, "w").write("\n".join(gcode)) + wx.CallAfter(self.loadfile, None, self.filename) + + def sdmenu(self, e): + obj = e.GetEventObject() + popupmenu = wx.Menu() + item = popupmenu.Append(-1, _("SD Upload")) + if not self.fgcode: + item.Enable(False) + self.Bind(wx.EVT_MENU, self.upload, id = item.GetId()) + item = popupmenu.Append(-1, _("SD Print")) + self.Bind(wx.EVT_MENU, self.sdprintfile, id = item.GetId()) + self.panel.PopupMenu(popupmenu, obj.GetPosition()) + + def htemp_change(self, event): + if self.hsetpoint > 0: + self.do_settemp("") + wx.CallAfter(self.htemp.SetInsertionPoint, 0) + + def btemp_change(self, event): + if self.bsetpoint > 0: + self.do_bedtemp("") + wx.CallAfter(self.btemp.SetInsertionPoint, 0) + + def tool_change(self, event): + self.do_tool(self.extrudersel.GetValue()) + + def show_viz_window(self, event): + if self.fgcode: + self.gwindow.Show(True) + self.gwindow.SetToolTip(wx.ToolTip("Mousewheel zooms the display\nShift / Mousewheel scrolls layers")) + self.gwindow.Raise() + + def setfeeds(self, e): + self.feedrates_changed = True + try: + if self.efeedc is not None: + self.settings._set("e_feedrate", self.efeedc.GetValue()) + except: + pass + try: + self.settings._set("z_feedrate", self.zfeedc.GetValue()) + except: + pass + try: + self.settings._set("xy_feedrate", self.xyfeedc.GetValue()) + except: + pass + try: + self.settings._set("last_extrusion", self.edist.GetValue()) + except: + pass + + def homeButtonClicked(self, axis): + # When user clicks on the XY control, the Z control no longer gets spacebar/repeat signals + self.zb.clearRepeat() + if axis == "x": + self.onecmd('home X') + elif axis == "y": # upper-right + self.onecmd('home Y') + elif axis == "z": + self.onecmd('home Z') + elif axis == "all": + self.onecmd('home') + elif axis == "center": + center_x = self.build_dimensions_list[0] / 2 + self.build_dimensions_list[3] + center_y = self.build_dimensions_list[1] / 2 + self.build_dimensions_list[4] + feed = self.settings.xy_feedrate + self.onecmd('G0 X%s Y%s F%s' % (center_x, center_y, feed)) + else: + return + self.p.send_now('M114') + + def clamped_move_message(self): + self.log(_("Manual move outside of the build volume prevented (see the \"Clamp manual moves\" option).")) + + def moveXY(self, x, y): + # When user clicks on the XY control, the Z control no longer gets spacebar/repeat signals + self.zb.clearRepeat() + if x != 0: + if self.settings.clamp_jogging: + new_x = self.current_pos[0] + x + if new_x < self.build_dimensions_list[3] or new_x > self.build_dimensions_list[0] + self.build_dimensions_list[3]: + self.clamped_move_message() + return + self.onecmd('move X %s' % x) + elif y != 0: + if self.settings.clamp_jogging: + new_y = self.current_pos[1] + y + if new_y < self.build_dimensions_list[4] or new_y > self.build_dimensions_list[1] + self.build_dimensions_list[4]: + self.clamped_move_message() + return + self.onecmd('move Y %s' % y) + else: + return + self.p.send_now('M114') + + def moveZ(self, z): + if z != 0: + if self.settings.clamp_jogging: + new_z = self.current_pos[2] + z + if new_z < self.build_dimensions_list[5] or new_z > self.build_dimensions_list[2] + self.build_dimensions_list[5]: + self.clamped_move_message() + return + self.onecmd('move Z %s' % z) + self.p.send_now('M114') + # When user clicks on the Z control, the XY control no longer gets spacebar/repeat signals + self.xyb.clearRepeat() + + def spacebarAction(self): + self.zb.repeatLast() + self.xyb.repeatLast() + + # -------------------------------------------------------------- + # Console handling + # -------------------------------------------------------------- + + def catchprint(self, l): + """Called by the Tee operator to write to the log box""" + if not self.IsFrozen(): + wx.CallAfter(self.addtexttolog, l) + + def addtexttolog(self, text): + try: + self.logbox.AppendText(text) + max_length = 20000 + current_length = self.logbox.GetLastPosition() + if current_length > max_length: + self.logbox.Remove(0, current_length / 10) + except: + self.log(_("Attempted to write invalid text to console, which could be due to an invalid baudrate")) + + def clear_log(self, e): + self.logbox.Clear() + + def set_verbose_communications(self, e): + self.p.loud = e.IsChecked() + + def sendline(self, e): + command = self.commandbox.GetValue() + if not len(command): + return + wx.CallAfter(self.addtexttolog, ">>> " + command + "\n") + line = self.precmd(str(command)) + self.onecmd(line) + self.commandbox.SetSelection(0, len(command)) + self.commandbox.history.append(command) + self.commandbox.histindex = len(self.commandbox.history) + + # -------------------------------------------------------------- + # Main menu handling & actions + # -------------------------------------------------------------- + + def create_menu(self): + """Create main menu""" + self.menustrip = wx.MenuBar() + # File menu + m = wx.Menu() + self.Bind(wx.EVT_MENU, self.loadfile, m.Append(-1, _("&Open..."), _(" Open file"))) + self.savebtn = m.Append(-1, _("&Save..."), _(" Save file")) + self.savebtn.Enable(False) + self.Bind(wx.EVT_MENU, self.savefile, self.savebtn) + + self.filehistory = wx.FileHistory(maxFiles = 8, idBase = wx.ID_FILE1) + recent = wx.Menu() + self.filehistory.UseMenu(recent) + self.Bind(wx.EVT_MENU_RANGE, self.load_recent_file, + id = wx.ID_FILE1, id2 = wx.ID_FILE9) + m.AppendMenu(wx.ID_ANY, _("&Recent Files"), recent) + self.Bind(wx.EVT_MENU, self.clear_log, m.Append(-1, _("Clear console"), _(" Clear output console"))) + self.Bind(wx.EVT_MENU, self.on_exit, m.Append(wx.ID_EXIT, _("E&xit"), _(" Closes the Window"))) + self.menustrip.Append(m, _("&File")) + + m = wx.Menu() + self.Bind(wx.EVT_MENU, self.do_editgcode, m.Append(-1, _("&Edit..."), _(" Edit open file"))) + self.Bind(wx.EVT_MENU, self.plate, m.Append(-1, _("Plater"), _(" Compose 3D models into a single plate"))) + self.Bind(wx.EVT_MENU, self.plate_gcode, m.Append(-1, _("G-Code Plater"), _(" Compose G-Codes into a single plate"))) + self.Bind(wx.EVT_MENU, self.exclude, m.Append(-1, _("Excluder"), _(" Exclude parts of the bed from being printed"))) + self.Bind(wx.EVT_MENU, self.project, m.Append(-1, _("Projector"), _(" Project slices"))) + self.menustrip.Append(m, _("&Tools")) + + m = wx.Menu() + self.recoverbtn = m.Append(-1, _("Recover"), _(" Recover previous print after a disconnect (homes X, Y, restores Z and E status)")) + self.recoverbtn.Disable = lambda *a: self.recoverbtn.Enable(False) + self.Bind(wx.EVT_MENU, self.recover, self.recoverbtn) + self.menustrip.Append(m, _("&Advanced")) + + if self.settings.slic3rintegration: + m = wx.Menu() + print_menu = wx.Menu() + filament_menu = wx.Menu() + printer_menu = wx.Menu() + m.AppendSubMenu(print_menu, _("Print &settings")) + m.AppendSubMenu(filament_menu, _("&Filament")) + m.AppendSubMenu(printer_menu, _("&Printer")) + menus = {"print": print_menu, + "filament": filament_menu, + "printer": printer_menu} + try: + self.load_slic3r_configs(menus) + self.menustrip.Append(m, _("&Slic3r")) + except IOError: + self.logError(_("Failed to load Slic3r configuration:") + + "\n" + traceback.format_exc()) + + # Settings menu + m = wx.Menu() + self.macros_menu = wx.Menu() + m.AppendSubMenu(self.macros_menu, _("&Macros")) + self.Bind(wx.EVT_MENU, self.new_macro, self.macros_menu.Append(-1, _("<&New...>"))) + self.Bind(wx.EVT_MENU, lambda *e: PronterOptions(self), m.Append(-1, _("&Options"), _(" Options dialog"))) + + self.Bind(wx.EVT_MENU, lambda x: threading.Thread(target = lambda: self.do_slice("set")).start(), m.Append(-1, _("Slicing settings"), _(" Adjust slicing settings"))) + + mItem = m.AppendCheckItem(-1, _("Debug communications"), + _("Print all G-code sent to and received from the printer.")) + m.Check(mItem.GetId(), self.p.loud) + self.Bind(wx.EVT_MENU, self.set_verbose_communications, mItem) + + self.menustrip.Append(m, _("&Settings")) + self.update_macros_menu() + self.SetMenuBar(self.menustrip) + + m = wx.Menu() + self.Bind(wx.EVT_MENU, self.about, + m.Append(-1, _("&About Printrun"), _("Show about dialog"))) + self.menustrip.Append(m, _("&Help")) + + def project(self, event): + """Start Projector tool""" + from printrun import projectlayer + projectlayer.SettingsFrame(self, self.p).Show() + + def exclude(self, event): + """Start part excluder tool""" + if not self.fgcode: + wx.CallAfter(self.statusbar.SetStatusText, _("No file loaded. Please use load first.")) + return + if not self.excluder: + from .excluder import Excluder + self.excluder = Excluder() + self.excluder.pop_window(self.fgcode, bgcolor = self.bgcolor, + build_dimensions = self.build_dimensions_list) + + def about(self, event): + """Show about dialog""" + + info = wx.AboutDialogInfo() + + info.SetIcon(wx.Icon(iconfile("pronterface.png"), wx.BITMAP_TYPE_PNG)) + info.SetName('Printrun') + info.SetVersion(printcore.__version__) + + description = _("Printrun is a pure Python 3D printing" + " (and other types of CNC) host software.") + + description += "\n\n" + \ + _("%.02fmm of filament have been extruded during prints") \ + % self.settings.total_filament_used + + info.SetDescription(description) + info.SetCopyright('(C) 2011 - 2015') + info.SetWebSite('https://github.com/kliment/Printrun') + + licence = """\ +Printrun is free software: you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any later +version. + +Printrun is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +Printrun. If not, see .""" + + info.SetLicence(licence) + info.AddDeveloper('Kliment Yanev') + info.AddDeveloper('Guillaume Seguin') + + wx.AboutBox(info) + + # -------------------------------------------------------------- + # Settings & command line handling (including update callbacks) + # -------------------------------------------------------------- + + def _add_settings(self, size): + self.settings._add(BooleanSetting("monitor", True, _("Monitor printer status"), _("Regularly monitor printer temperatures (required to have functional temperature graph or gauges)"), "Printer"), self.update_monitor) + self.settings._add(StringSetting("simarrange_path", "", _("Simarrange command"), _("Path to the simarrange binary to use in the STL plater"), "External")) + self.settings._add(BooleanSetting("circular_bed", False, _("Circular build platform"), _("Draw a circular (or oval) build platform instead of a rectangular one"), "Printer"), self.update_bed_viz) + self.settings._add(SpinSetting("extruders", 0, 1, 5, _("Extruders count"), _("Number of extruders"), "Printer")) + self.settings._add(BooleanSetting("clamp_jogging", False, _("Clamp manual moves"), _("Prevent manual moves from leaving the specified build dimensions"), "Printer")) + self.settings._add(ComboSetting("uimode", _("Standard"), [_("Standard"), _("Compact"), _("Tabbed"), _("Tabbed with platers")], _("Interface mode"), _("Standard interface is a one-page, three columns layout with controls/visualization/log\nCompact mode is a one-page, two columns layout with controls + log/visualization\nTabbed mode is a two-pages mode, where the first page shows controls and the second one shows visualization and log.\nTabbed with platers mode is the same as Tabbed, but with two extra pages for the STL and G-Code platers."), "UI"), self.reload_ui) + self.settings._add(ComboSetting("controlsmode", "Standard", ["Standard", "Mini"], _("Controls mode"), _("Standard controls include all controls needed for printer setup and calibration, while Mini controls are limited to the ones needed for daily printing"), "UI"), self.reload_ui) + self.settings._add(BooleanSetting("slic3rintegration", False, _("Enable Slic3r integration"), _("Add a menu to select Slic3r profiles directly from Pronterface"), "UI"), self.reload_ui) + self.settings._add(BooleanSetting("slic3rupdate", False, _("Update Slic3r default presets"), _("When selecting a profile in Slic3r integration menu, also save it as the default Slic3r preset"), "UI")) + self.settings._add(ComboSetting("mainviz", "3D", ["2D", "3D", "None"], _("Main visualization"), _("Select visualization for main window."), "Viewer"), self.reload_ui) + self.settings._add(BooleanSetting("viz3d", False, _("Use 3D in GCode viewer window"), _("Use 3D mode instead of 2D layered mode in the visualization window"), "Viewer"), self.reload_ui) + self.settings._add(StaticTextSetting("separator_3d_viewer", _("3D viewer options"), "", group = "Viewer")) + self.settings._add(BooleanSetting("light3d", False, _("Use a lighter 3D visualization"), _("Use a lighter visualization with simple lines instead of extruded paths for 3D viewer"), "Viewer"), self.reload_ui) + self.settings._add(ComboSetting("antialias3dsamples", "0", ["0", "2", "4", "8"], _("Number of anti-aliasing samples"), _("Amount of anti-aliasing samples used in the 3D viewer"), "Viewer"), self.reload_ui) + self.settings._add(BooleanSetting("trackcurrentlayer3d", False, _("Track current layer in main 3D view"), _("Track the currently printing layer in the main 3D visualization"), "Viewer")) + self.settings._add(FloatSpinSetting("gcview_path_width", 0.4, 0.01, 2, _("Extrusion width for 3D viewer"), _("Width of printed path in 3D viewer"), "Viewer", increment = 0.05), self.update_gcview_params) + self.settings._add(FloatSpinSetting("gcview_path_height", 0.3, 0.01, 2, _("Layer height for 3D viewer"), _("Height of printed path in 3D viewer"), "Viewer", increment = 0.05), self.update_gcview_params) + self.settings._add(BooleanSetting("tempgraph", True, _("Display temperature graph"), _("Display time-lapse temperature graph"), "UI"), self.reload_ui) + self.settings._add(BooleanSetting("tempgauges", False, _("Display temperature gauges"), _("Display graphical gauges for temperatures visualization"), "UI"), self.reload_ui) + self.settings._add(BooleanSetting("lockbox", False, _("Display interface lock checkbox"), _("Display a checkbox that, when check, locks most of Pronterface"), "UI"), self.reload_ui) + self.settings._add(BooleanSetting("lockonstart", False, _("Lock interface upon print start"), _("If lock checkbox is enabled, lock the interface when starting a print"), "UI")) + self.settings._add(BooleanSetting("refreshwhenloading", True, _("Update UI during G-Code load"), _("Regularly update visualization during the load of a G-Code file"), "UI")) + self.settings._add(HiddenSetting("last_window_width", size[0])) + self.settings._add(HiddenSetting("last_window_height", size[1])) + self.settings._add(HiddenSetting("last_window_maximized", False)) + self.settings._add(HiddenSetting("last_sash_position", -1)) + self.settings._add(HiddenSetting("last_bed_temperature", 0.0)) + self.settings._add(HiddenSetting("last_file_path", u"")) + self.settings._add(HiddenSetting("last_file_filter", 0)) + self.settings._add(HiddenSetting("last_temperature", 0.0)) + self.settings._add(StaticTextSetting("separator_2d_viewer", _("2D viewer options"), "", group = "Viewer")) + self.settings._add(FloatSpinSetting("preview_extrusion_width", 0.5, 0, 10, _("Preview extrusion width"), _("Width of Extrusion in Preview"), "Viewer", increment = 0.1), self.update_gviz_params) + self.settings._add(SpinSetting("preview_grid_step1", 10., 0, 200, _("Fine grid spacing"), _("Fine Grid Spacing"), "Viewer"), self.update_gviz_params) + self.settings._add(SpinSetting("preview_grid_step2", 50., 0, 200, _("Coarse grid spacing"), _("Coarse Grid Spacing"), "Viewer"), self.update_gviz_params) + self.settings._add(StringSetting("bgcolor", "#FFFFFF", _("Background color"), _("Pronterface background color"), "Colors"), self.reload_ui, validate = check_rgb_color) + self.settings._add(StringSetting("gcview_color_background", "#FAFAC7FF", _("3D view background color"), _("Color of the 3D view background"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StringSetting("gcview_color_travel", "#99999999", _("3D view travel moves color"), _("Color of travel moves in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StringSetting("gcview_color_tool0", "#FF000099", _("3D view print moves color"), _("Color of print moves with tool 0 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StringSetting("gcview_color_tool1", "#AC0DFF99", _("3D view tool 1 moves color"), _("Color of print moves with tool 1 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StringSetting("gcview_color_tool2", "#FFCE0099", _("3D view tool 2 moves color"), _("Color of print moves with tool 2 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StringSetting("gcview_color_tool3", "#FF009F99", _("3D view tool 3 moves color"), _("Color of print moves with tool 3 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StringSetting("gcview_color_tool4", "#00FF8F99", _("3D view tool 4 moves color"), _("Color of print moves with tool 4 in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StringSetting("gcview_color_printed", "#33BF0099", _("3D view printed moves color"), _("Color of printed moves in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StringSetting("gcview_color_current", "#00E5FFCC", _("3D view current layer moves color"), _("Color of moves in current layer in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StringSetting("gcview_color_current_printed", "#196600CC", _("3D view printed current layer moves color"), _("Color of already printed moves from current layer in 3D view"), "Colors"), self.update_gcview_colors, validate = check_rgba_color) + self.settings._add(StaticTextSetting("note1", _("Note:"), _("Changing some of these settings might require a restart to get effect"), group = "UI")) + recentfilessetting = StringSetting("recentfiles", "[]") + recentfilessetting.hidden = True + self.settings._add(recentfilessetting, self.update_recent_files) + + def add_cmdline_arguments(self, parser): + pronsole.pronsole.add_cmdline_arguments(self, parser) + parser.add_argument('-a', '--autoconnect', help = _("automatically try to connect to printer on startup"), action = "store_true") + + def process_cmdline_arguments(self, args): + pronsole.pronsole.process_cmdline_arguments(self, args) + self.autoconnect = args.autoconnect + + def update_recent_files(self, param, value): + if self.filehistory is None: + return + recent_files = [] + try: + recent_files = json.loads(value) + except: + self.logError(_("Failed to load recent files list:") + + "\n" + traceback.format_exc()) + # Clear history + while self.filehistory.GetCount(): + self.filehistory.RemoveFileFromHistory(0) + recent_files.reverse() + for f in recent_files: + self.filehistory.AddFileToHistory(f) + + def update_gviz_params(self, param, value): + params_map = {"preview_extrusion_width": "extrusion_width", + "preview_grid_step1": "grid", + "preview_grid_step2": "grid"} + if param not in params_map: + return + if not hasattr(self, "gviz"): + # GUI hasn't been loaded yet, ignore this setting + return + trueparam = params_map[param] + if hasattr(self.gviz, trueparam): + gviz = self.gviz + elif hasattr(self.gwindow, "p") and hasattr(self.gwindow.p, trueparam): + gviz = self.gwindow.p + else: + return + if trueparam == "grid": + try: + item = int(param[-1]) # extract list item position + grid = list(gviz.grid) + grid[item - 1] = value + value = tuple(grid) + except: + self.logError(traceback.format_exc()) + if hasattr(self.gviz, trueparam): + self.apply_gviz_params(self.gviz, trueparam, value) + if hasattr(self.gwindow, "p") and hasattr(self.gwindow.p, trueparam): + self.apply_gviz_params(self.gwindow.p, trueparam, value) + + def apply_gviz_params(self, widget, param, value): + setattr(widget, param, value) + widget.dirty = 1 + wx.CallAfter(widget.Refresh) + + def update_gcview_colors(self, param, value): + color = hexcolor_to_float(value, 4) + # This is sort of a hack: we copy the color values into the preexisting + # color tuple so that we don't need to update the tuple used by gcview + target_color = getattr(self, param) + for i, v in enumerate(color): + target_color[i] = v + wx.CallAfter(self.Refresh) + + def update_build_dimensions(self, param, value): + pronsole.pronsole.update_build_dimensions(self, param, value) + self.update_bed_viz() + + def update_bed_viz(self, *args): + """Update bed visualization when size/type changed""" + if hasattr(self, "gviz") and hasattr(self.gviz, "recreate_platform"): + self.gviz.recreate_platform(self.build_dimensions_list, self.settings.circular_bed) + if hasattr(self, "gwindow") and hasattr(self.gwindow, "recreate_platform"): + self.gwindow.recreate_platform(self.build_dimensions_list, self.settings.circular_bed) + + def update_gcview_params(self, *args): + need_reload = False + if hasattr(self, "gviz") and hasattr(self.gviz, "set_gcview_params"): + need_reload |= self.gviz.set_gcview_params(self.settings.gcview_path_width, self.settings.gcview_path_height) + if hasattr(self, "gwindow") and hasattr(self.gwindow, "set_gcview_params"): + need_reload |= self.gwindow.set_gcview_params(self.settings.gcview_path_width, self.settings.gcview_path_height) + if need_reload: + self.start_viz_thread() + + def update_monitor(self, *args): + if hasattr(self, "graph") and self.display_graph: + if self.settings.monitor: + wx.CallAfter(self.graph.StartPlotting, 1000) + else: + wx.CallAfter(self.graph.StopPlotting) + + # -------------------------------------------------------------- + # Statusbar handling + # -------------------------------------------------------------- + + def statuschecker_inner(self): + status_string = "" + if self.sdprinting or self.uploading or self.p.printing: + secondsremain, secondsestimate, progress = self.get_eta() + if self.sdprinting or self.uploading: + if self.uploading: + status_string += _("SD upload: %04.2f%% |") % (100 * progress,) + status_string += _(" Line# %d of %d lines |") % (self.p.queueindex, len(self.p.mainqueue)) + else: + status_string += _("SD printing: %04.2f%% |") % (self.percentdone,) + elif self.p.printing: + status_string += _("Printing: %04.2f%% |") % (100 * float(self.p.queueindex) / len(self.p.mainqueue),) + status_string += _(" Line# %d of %d lines |") % (self.p.queueindex, len(self.p.mainqueue)) + if progress > 0: + status_string += _(" Est: %s of %s remaining | ") % (format_duration(secondsremain), + format_duration(secondsestimate)) + status_string += _(" Z: %.3f mm") % self.curlayer + elif self.loading_gcode: + status_string = self.loading_gcode_message + wx.CallAfter(self.statusbar.SetStatusText, status_string) + wx.CallAfter(self.gviz.Refresh) + # Call pronsole's statuschecker inner loop function to handle + # temperature monitoring and status loop sleep + pronsole.pronsole.statuschecker_inner(self, self.settings.monitor) + try: + while not self.sentglines.empty(): + gc = self.sentglines.get_nowait() + wx.CallAfter(self.gviz.addgcodehighlight, gc) + self.sentglines.task_done() + except Queue.Empty: + pass + + def statuschecker(self): + pronsole.pronsole.statuschecker(self) + wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer.")) + + # -------------------------------------------------------------- + # Interface lock handling + # -------------------------------------------------------------- + + def lock(self, event = None, force = None): + if force is not None: + self.locker.SetValue(force) + if self.locker.GetValue(): + self.log(_("Locking interface.")) + for panel in self.panels: + panel.Disable() + else: + self.log(_("Unlocking interface.")) + for panel in self.panels: + panel.Enable() + + # -------------------------------------------------------------- + # Printer connection handling + # -------------------------------------------------------------- + + def connect(self, event = None): + self.log(_("Connecting...")) + port = None + if self.serialport.GetValue(): + port = str(self.serialport.GetValue()) + else: + scanned = self.scanserial() + if scanned: + port = scanned[0] + baud = 115200 + try: + baud = int(self.baud.GetValue()) + except: + self.logError(_("Could not parse baud rate: ") + + "\n" + traceback.format_exc()) + if self.paused: + self.p.paused = 0 + self.p.printing = 0 + wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) + wx.CallAfter(self.printbtn.SetLabel, _("Print")) + wx.CallAfter(self.toolbarsizer.Layout) + self.paused = 0 + if self.sdprinting: + self.p.send_now("M26 S0") + if not self.connect_to_printer(port, baud, self.settings.dtr): + return + if port != self.settings.port: + self.set("port", port) + if baud != self.settings.baudrate: + self.set("baudrate", str(baud)) + if self.predisconnect_mainqueue: + self.recoverbtn.Enable() + + def store_predisconnect_state(self): + self.predisconnect_mainqueue = self.p.mainqueue + self.predisconnect_queueindex = self.p.queueindex + self.predisconnect_layer = self.curlayer + + def disconnect(self, event = None): + self.log(_("Disconnected.")) + if self.p.printing or self.p.paused or self.paused: + self.store_predisconnect_state() + self.p.disconnect() + self.statuscheck = False + if self.status_thread: + self.status_thread.join() + self.status_thread = None + + wx.CallAfter(self.connectbtn.SetLabel, _("Connect")) + wx.CallAfter(self.connectbtn.SetToolTip, wx.ToolTip(_("Connect to the printer"))) + wx.CallAfter(self.connectbtn.Bind, wx.EVT_BUTTON, self.connect) + + wx.CallAfter(self.gui_set_disconnected) + + if self.paused: + self.p.paused = 0 + self.p.printing = 0 + wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) + wx.CallAfter(self.printbtn.SetLabel, _("Print")) + self.paused = 0 + if self.sdprinting: + self.p.send_now("M26 S0") + + # Relayout the toolbar to handle new buttons size + wx.CallAfter(self.toolbarsizer.Layout) + + def reset(self, event): + self.log(_("Reset.")) + dlg = wx.MessageDialog(self, _("Are you sure you want to reset the printer?"), _("Reset?"), wx.YES | wx.NO) + if dlg.ShowModal() == wx.ID_YES: + self.p.reset() + self.sethotendgui(0) + self.setbedgui(0) + self.p.printing = 0 + wx.CallAfter(self.printbtn.SetLabel, _("Print")) + if self.paused: + self.p.paused = 0 + wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) + self.paused = 0 + wx.CallAfter(self.toolbarsizer.Layout) + dlg.Destroy() + + # -------------------------------------------------------------- + # Print/upload handling + # -------------------------------------------------------------- + + def on_startprint(self): + wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) + wx.CallAfter(self.pausebtn.Enable) + wx.CallAfter(self.printbtn.SetLabel, _("Restart")) + wx.CallAfter(self.toolbarsizer.Layout) + + def printfile(self, event): + self.extra_print_time = 0 + if self.paused: + self.p.paused = 0 + self.paused = 0 + if self.sdprinting: + self.on_startprint() + self.p.send_now("M26 S0") + self.p.send_now("M24") + return + + if not self.fgcode: + wx.CallAfter(self.statusbar.SetStatusText, _("No file loaded. Please use load first.")) + return + if not self.p.online: + wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer.")) + return + self.sdprinting = False + self.on_startprint() + self.p.startprint(self.fgcode) + + def sdprintfile(self, event): + self.extra_print_time = 0 + self.on_startprint() + threading.Thread(target = self.getfiles).start() + + def upload(self, event): + if not self.fgcode: + return + if not self.p.online: + return + dlg = wx.TextEntryDialog(self, ("Enter a target filename in 8.3 format:"), _("Pick SD filename"), dosify(self.filename)) + if dlg.ShowModal() == wx.ID_OK: + self.p.send_now("M21") + self.p.send_now("M28 " + str(dlg.GetValue())) + self.recvlisteners.append(self.uploadtrigger) + dlg.Destroy() + + def uploadtrigger(self, l): + if "Writing to file" in l: + self.uploading = True + self.p.startprint(self.fgcode) + self.p.endcb = self.endupload + self.recvlisteners.remove(self.uploadtrigger) + elif "open failed, File" in l: + self.recvlisteners.remove(self.uploadtrigger) + + def endupload(self): + self.p.send_now("M29 ") + wx.CallAfter(self.statusbar.SetStatusText, _("File upload complete")) + time.sleep(0.5) + self.p.clear = True + self.uploading = False + + def pause(self, event = None): + if not self.paused: + self.log(_("Print paused at: %s") % format_time(time.time())) + if self.sdprinting: + self.p.send_now("M25") + else: + if not self.p.printing: + return + self.p.pause() + self.p.runSmallScript(self.pauseScript) + self.paused = True + # self.p.runSmallScript(self.pauseScript) + self.extra_print_time += int(time.time() - self.starttime) + wx.CallAfter(self.pausebtn.SetLabel, _("Resume")) + wx.CallAfter(self.toolbarsizer.Layout) + else: + self.log(_("Resuming.")) + self.paused = False + if self.sdprinting: + self.p.send_now("M24") + else: + self.p.resume() + wx.CallAfter(self.pausebtn.SetLabel, _("Pause")) + wx.CallAfter(self.toolbarsizer.Layout) + + def recover(self, event): + self.extra_print_time = 0 + if not self.p.online: + wx.CallAfter(self.statusbar.SetStatusText, _("Not connected to printer.")) + return + # Reset Z + self.p.send_now("G92 Z%f" % self.predisconnect_layer) + # Home X and Y + self.p.send_now("G28 X Y") + self.on_startprint() + self.p.startprint(self.predisconnect_mainqueue, self.p.queueindex) + + # -------------------------------------------------------------- + # File loading handling + # -------------------------------------------------------------- + + def filesloaded(self): + dlg = wx.SingleChoiceDialog(self, _("Select the file to print"), _("Pick SD file"), self.sdfiles) + if dlg.ShowModal() == wx.ID_OK: + target = dlg.GetStringSelection() + if len(target): + self.recvlisteners.append(self.waitforsdresponse) + self.p.send_now("M23 " + target.lower()) + dlg.Destroy() + + def getfiles(self): + if not self.p.online: + self.sdfiles = [] + return + self.sdlisting = 0 + self.sdfiles = [] + self.recvlisteners.append(self.listfiles) + self.p.send_now("M21") + self.p.send_now("M20") + + def model_to_gcode_filename(self, filename): + suffix = "_export.gcode" + for ext in [".stl", ".obj"]: + filename = filename.replace(ext, suffix) + filename = filename.replace(ext.upper(), suffix) + return filename + + def slice_func(self): + try: + output_filename = self.model_to_gcode_filename(self.filename) + pararray = prepare_command(self.settings.slicecommand, + {"$s": self.filename, "$o": output_filename}) + if self.settings.slic3rintegration: + for cat, config in self.slic3r_configs.items(): + if config: + fpath = os.path.join(self.slic3r_configpath, cat, config) + pararray += ["--load", fpath] + self.log(_("Running ") + " ".join(pararray)) + self.slicep = subprocess.Popen(pararray, stderr = subprocess.STDOUT, stdout = subprocess.PIPE) + while True: + o = self.slicep.stdout.read(1) + if o == '' and self.slicep.poll() is not None: break + sys.stdout.write(o) + self.slicep.wait() + self.stopsf = 1 + except: + self.logError(_("Failed to execute slicing software: ") + + "\n" + traceback.format_exc()) + self.stopsf = 1 + + def slice_monitor(self): + while not self.stopsf: + try: + wx.CallAfter(self.statusbar.SetStatusText, _("Slicing...")) # +self.cout.getvalue().split("\n")[-1]) + except: + pass + time.sleep(0.1) + fn = self.filename + try: + self.load_gcode_async(self.model_to_gcode_filename(self.filename)) + except: + self.filename = fn + self.slicing = False + self.slicep = None + + def slice(self, filename): + wx.CallAfter(self.loadbtn.SetLabel, _("Cancel")) + wx.CallAfter(self.toolbarsizer.Layout) + self.log(_("Slicing ") + filename) + self.cout = StringIO.StringIO() + self.filename = filename + self.stopsf = 0 + self.slicing = True + threading.Thread(target = self.slice_func).start() + threading.Thread(target = self.slice_monitor).start() + + def cmdline_filename_callback(self, filename): + # Do nothing when processing a filename from command line, as we'll + # handle it when everything has been prepared + self.filename = filename + + def do_load(self, l): + if hasattr(self, 'slicing'): + self.loadfile(None, l) + else: + self._do_load(l) + + def load_recent_file(self, event): + fileid = event.GetId() - wx.ID_FILE1 + path = self.filehistory.GetHistoryFile(fileid) + self.loadfile(None, filename = path) + + def loadfile(self, event, filename = None): + if self.slicing and self.slicep is not None: + self.slicep.terminate() + return + basedir = self.settings.last_file_path + if not os.path.exists(basedir): + basedir = "." + try: + basedir = os.path.split(self.filename)[0] + except: + pass + dlg = None + if filename is None: + dlg = wx.FileDialog(self, _("Open file to print"), basedir, style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + dlg.SetWildcard(_("OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|GCODE files (*.gcode;*.gco;*.g)|*.gcode;*.gco;*.g|OBJ, STL files (*.stl;*.STL;*.obj;*.OBJ)|*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*")) + try: + dlg.SetFilterIndex(self.settings.last_file_filter) + except: + pass + if filename or dlg.ShowModal() == wx.ID_OK: + if filename: + name = filename + else: + name = dlg.GetPath() + self.set("last_file_filter", dlg.GetFilterIndex()) + dlg.Destroy() + if not os.path.exists(name): + self.statusbar.SetStatusText(_("File not found!")) + return + path = os.path.split(name)[0] + if path != self.settings.last_file_path: + self.set("last_file_path", path) + try: + abspath = os.path.abspath(name) + recent_files = [] + try: + recent_files = json.loads(self.settings.recentfiles) + except: + self.logError(_("Failed to load recent files list:") + + "\n" + traceback.format_exc()) + if abspath in recent_files: + recent_files.remove(abspath) + recent_files.insert(0, abspath) + if len(recent_files) > 5: + recent_files = recent_files[:5] + self.set("recentfiles", json.dumps(recent_files)) + except: + self.logError(_("Could not update recent files list:") + + "\n" + traceback.format_exc()) + if name.lower().endswith(".stl") or name.lower().endswith(".obj"): + self.slice(name) + else: + self.load_gcode_async(name) + else: + dlg.Destroy() + + def load_gcode_async(self, filename): + self.filename = filename + gcode = self.pre_gcode_load() + self.log(_("Loading file: %s") % filename) + threading.Thread(target = self.load_gcode_async_thread, args = (gcode,)).start() + + def load_gcode_async_thread(self, gcode): + try: + self.load_gcode(self.filename, + layer_callback = self.layer_ready_cb, + gcode = gcode) + except PronterfaceQuitException: + return + wx.CallAfter(self.post_gcode_load) + + def layer_ready_cb(self, gcode, layer): + global pronterface_quitting + if pronterface_quitting: + raise PronterfaceQuitException + if not self.settings.refreshwhenloading: + return + self.viz_last_layer = layer + if time.time() - self.viz_last_yield > 1.0: + time.sleep(0.2) + self.loading_gcode_message = _("Loading %s: %d layers loaded (%d lines)") % (self.filename, layer + 1, len(gcode)) + self.viz_last_yield = time.time() + wx.CallAfter(self.statusbar.SetStatusText, self.loading_gcode_message) + + def start_viz_thread(self, gcode = None): + threading.Thread(target = self.loadviz, args = (gcode,)).start() + + def pre_gcode_load(self): + self.loading_gcode = True + self.loading_gcode_message = _("Loading %s...") % self.filename + if self.settings.mainviz == "None": + gcode = gcoder.LightGCode(deferred = True) + else: + gcode = gcoder.GCode(deferred = True) + self.viz_last_yield = 0 + self.viz_last_layer = -1 + self.start_viz_thread(gcode) + return gcode + + def post_gcode_load(self, print_stats = True): + # Must be called in wx.CallAfter for safety + self.loading_gcode = False + self.SetTitle(_(u"Pronterface - %s") % self.filename) + message = _("Loaded %s, %d lines") % (self.filename, len(self.fgcode),) + self.log(message) + self.statusbar.SetStatusText(message) + self.savebtn.Enable(True) + self.loadbtn.SetLabel(_("Load File")) + self.printbtn.SetLabel(_("Print")) + self.pausebtn.SetLabel(_("Pause")) + self.pausebtn.Disable() + self.recoverbtn.Disable() + if self.p.online: + self.printbtn.Enable() + self.toolbarsizer.Layout() + self.viz_last_layer = None + if print_stats: + self.output_gcode_stats() + + def output_gcode_stats(self): + gcode = self.fgcode + self.log(_("%.2fmm of filament used in this print") % gcode.filament_length) + if(len(gcode.filament_length_multi)>1): + for i in enumerate(gcode.filament_length_multi): + print "Extruder %d: %0.02fmm" % (i[0],i[1]) + self.log(_("The print goes:")) + self.log(_("- from %.2f mm to %.2f mm in X and is %.2f mm wide") % (gcode.xmin, gcode.xmax, gcode.width)) + self.log(_("- from %.2f mm to %.2f mm in Y and is %.2f mm deep") % (gcode.ymin, gcode.ymax, gcode.depth)) + self.log(_("- from %.2f mm to %.2f mm in Z and is %.2f mm high") % (gcode.zmin, gcode.zmax, gcode.height)) + self.log(_("Estimated duration: %d layers, %s") % gcode.estimate_duration()) + + def loadviz(self, gcode = None): + self.gviz.clear() + self.gwindow.p.clear() + if gcode is not None: + generator = self.gviz.addfile_perlayer(gcode, True) + next_layer = 0 + # Progressive loading of visualization + # We load layers up to the last one which has been processed in GCoder + # (self.viz_last_layer) + # Once the GCode has been entirely loaded, this variable becomes None, + # indicating that we can do the last generator call to finish the + # loading of the visualization, which will itself return None. + # During preloading we verify that the layer we added is the one we + # expected through the assert call. + while True: + global pronterface_quitting + if pronterface_quitting: + return + max_layer = self.viz_last_layer + if max_layer is None: + break + while next_layer <= max_layer: + assert(generator.next() == next_layer) + next_layer += 1 + time.sleep(0.1) + generator_output = generator.next() + while generator_output is not None: + assert(generator_output in (None, next_layer)) + next_layer += 1 + generator_output = generator.next() + else: + # If GCode is not being loaded asynchroneously, it is already + # loaded, so let's make visualization sequentially + gcode = self.fgcode + self.gviz.addfile(gcode) + wx.CallAfter(self.gviz.Refresh) + # Load external window sequentially now that everything is ready. + # We can't really do any better as the 3D viewer might clone the + # finalized model from the main visualization + self.gwindow.p.addfile(gcode) + + # -------------------------------------------------------------- + # File saving handling + # -------------------------------------------------------------- + + def savefile(self, event): + basedir = self.settings.last_file_path + if not os.path.exists(basedir): + basedir = "." + try: + basedir = os.path.split(self.filename)[0] + except: + pass + dlg = wx.FileDialog(self, _("Save as"), basedir, style = wx.FD_SAVE) + dlg.SetWildcard(_("GCODE files (*.gcode;*.gco;*.g)|*.gcode;*.gco;*.g|All Files (*.*)|*.*")) + if dlg.ShowModal() == wx.ID_OK: + name = dlg.GetPath() + open(name, "w").write("\n".join((line.raw for line in self.fgcode))) + self.log(_("G-Code succesfully saved to %s") % name) + dlg.Destroy() + + # -------------------------------------------------------------- + # Printcore callbacks + # -------------------------------------------------------------- + + def process_host_command(self, command): + """Override host command handling""" + command = command.lstrip() + if command.startswith(";@pause"): + self.pause(None) + else: + pronsole.pronsole.process_host_command(self, command) + + def startcb(self, resuming = False): + """Callback on print start""" + pronsole.pronsole.startcb(self, resuming) + if self.settings.lockbox and self.settings.lockonstart: + wx.CallAfter(self.lock, force = True) + + def endcb(self): + """Callback on print end/pause""" + pronsole.pronsole.endcb(self) + if self.p.queueindex == 0: + self.p.runSmallScript(self.endScript) + wx.CallAfter(self.pausebtn.Disable) + wx.CallAfter(self.printbtn.SetLabel, _("Print")) + wx.CallAfter(self.toolbarsizer.Layout) + + def online(self): + """Callback when printer goes online""" + self.log(_("Printer is now online.")) + wx.CallAfter(self.online_gui) + + def online_gui(self): + """Callback when printer goes online (graphical bits)""" + self.connectbtn.SetLabel(_("Disconnect")) + self.connectbtn.SetToolTip(wx.ToolTip("Disconnect from the printer")) + self.connectbtn.Bind(wx.EVT_BUTTON, self.disconnect) + + if hasattr(self, "extrudersel"): + self.do_tool(self.extrudersel.GetValue()) + + self.gui_set_connected() + + if self.filename: + self.printbtn.Enable() + + wx.CallAfter(self.toolbarsizer.Layout) + + def sentcb(self, line, gline): + """Callback when a printer gcode has been sent""" + if not gline: + pass + elif gline.command in ["M104", "M109"]: + gline_s = gcoder.S(gline) + if gline_s is not None: + temp = gline_s + if self.display_gauges: wx.CallAfter(self.hottgauge.SetTarget, temp) + if self.display_graph: wx.CallAfter(self.graph.SetExtruder0TargetTemperature, temp) + elif gline.command in ["M140", "M190"]: + gline_s = gcoder.S(gline) + if gline_s is not None: + temp = gline_s + if self.display_gauges: wx.CallAfter(self.bedtgauge.SetTarget, temp) + if self.display_graph: wx.CallAfter(self.graph.SetBedTargetTemperature, temp) + elif gline.command in ["M106"]: + gline_s=gcoder.S(gline) + fanpow=255 + if gline_s is not None: + fanpow=gline_s + if self.display_graph: wx.CallAfter(self.graph.SetFanPower, fanpow) + elif gline.command in ["M107"]: + if self.display_graph: wx.CallAfter(self.graph.SetFanPower, 0) + elif gline.command.startswith("T"): + tool = gline.command[1:] + if hasattr(self, "extrudersel"): wx.CallAfter(self.extrudersel.SetValue, tool) + if gline.is_move: + self.sentglines.put_nowait(gline) + + def is_excluded_move(self, gline): + """Check whether the given moves ends at a position specified as + excluded in the part excluder""" + if not gline.is_move or not self.excluder or not self.excluder.rectangles: + return False + for (x0, y0, x1, y1) in self.excluder.rectangles: + if x0 <= gline.current_x <= x1 and y0 <= gline.current_y <= y1: + return True + return False + + def preprintsendcb(self, gline, next_gline): + """Callback when a printer gcode is about to be sent. We use it to + exclude moves defined by the part excluder tool""" + if not self.is_excluded_move(gline): + return gline + else: + if gline.z is not None: + if gline.relative: + if self.excluder_z_abs is not None: + self.excluder_z_abs += gline.z + elif self.excluder_z_rel is not None: + self.excluder_z_rel += gline.z + else: + self.excluder_z_rel = gline.z + else: + self.excluder_z_rel = None + self.excluder_z_abs = gline.z + if gline.e is not None and not gline.relative_e: + self.excluder_e = gline.e + # If next move won't be excluded, push the changes we have to do + if next_gline is not None and not self.is_excluded_move(next_gline): + if self.excluder_e is not None: + self.p.send_now("G92 E%.5f" % self.excluder_e) + self.excluder_e = None + if self.excluder_z_abs is not None: + if gline.relative: + self.p.send_now("G90") + self.p.send_now("G1 Z%.5f" % self.excluder_z_abs) + self.excluder_z_abs = None + if gline.relative: + self.p.send_now("G91") + if self.excluder_z_rel is not None: + if not gline.relative: + self.p.send_now("G91") + self.p.send_now("G1 Z%.5f" % self.excluder_z_rel) + self.excluder_z_rel = None + if not gline.relative: + self.p.send_now("G90") + return None + + def printsentcb(self, gline): + """Callback when a print gcode has been sent""" + if gline.is_move: + if hasattr(self.gwindow, "set_current_gline"): + wx.CallAfter(self.gwindow.set_current_gline, gline) + if hasattr(self.gviz, "set_current_gline"): + wx.CallAfter(self.gviz.set_current_gline, gline) + + def layer_change_cb(self, newlayer): + """Callback when the printed layer changed""" + pronsole.pronsole.layer_change_cb(self, newlayer) + if self.settings.mainviz != "3D" or self.settings.trackcurrentlayer3d: + wx.CallAfter(self.gviz.setlayer, newlayer) + + def update_tempdisplay(self): + try: + temps = parse_temperature_report(self.tempreadings) + if "T0" in temps and temps["T0"][0]: + hotend_temp = float(temps["T0"][0]) + elif "T" in temps and temps["T"][0]: + hotend_temp = float(temps["T"][0]) + else: + hotend_temp = None + if hotend_temp is not None: + if self.display_graph: wx.CallAfter(self.graph.SetExtruder0Temperature, hotend_temp) + if self.display_gauges: wx.CallAfter(self.hottgauge.SetValue, hotend_temp) + setpoint = None + if "T0" in temps and temps["T0"][1]: setpoint = float(temps["T0"][1]) + elif temps["T"][1]: setpoint = float(temps["T"][1]) + if setpoint is not None: + if self.display_graph: wx.CallAfter(self.graph.SetExtruder0TargetTemperature, setpoint) + if self.display_gauges: wx.CallAfter(self.hottgauge.SetTarget, setpoint) + if "T1" in temps: + hotend_temp = float(temps["T1"][0]) + if self.display_graph: wx.CallAfter(self.graph.SetExtruder1Temperature, hotend_temp) + setpoint = temps["T1"][1] + if setpoint and self.display_graph: + wx.CallAfter(self.graph.SetExtruder1TargetTemperature, float(setpoint)) + bed_temp = float(temps["B"][0]) if "B" in temps and temps["B"][0] else None + if bed_temp is not None: + if self.display_graph: wx.CallAfter(self.graph.SetBedTemperature, bed_temp) + if self.display_gauges: wx.CallAfter(self.bedtgauge.SetValue, bed_temp) + setpoint = temps["B"][1] + if setpoint: + setpoint = float(setpoint) + if self.display_graph: wx.CallAfter(self.graph.SetBedTargetTemperature, setpoint) + if self.display_gauges: wx.CallAfter(self.bedtgauge.SetTarget, setpoint) + except: + self.logError(traceback.format_exc()) + + def update_pos(self): + bits = gcoder.m114_exp.findall(self.posreport) + x = None + y = None + z = None + for bit in bits: + if not bit[0]: continue + if x is None and bit[0] == "X": + x = float(bit[1]) + elif y is None and bit[0] == "Y": + y = float(bit[1]) + elif z is None and bit[0] == "Z": + z = float(bit[1]) + if x is not None: self.current_pos[0] = x + if y is not None: self.current_pos[1] = y + if z is not None: self.current_pos[2] = z + + def recvcb_actions(self, l): + if l.startswith("!!"): + if not self.paused: + wx.CallAfter(self.pause) + msg = l.split(" ", 1) + if len(msg) > 1 and not self.p.loud: + wx.CallAfter(self.addtexttolog, msg[1] + "\n") + return True + elif l.startswith("//"): + command = l.split(" ", 1) + if len(command) > 1: + command = command[1] + command = command.split(":") + if len(command) == 2 and command[0] == "action": + command = command[1] + self.log(_("Received command %s") % command) + if command == "pause": + if not self.paused: + wx.CallAfter(self.pause) + return True + elif command == "resume": + if self.paused: + wx.CallAfter(self.pause) + return True + elif command == "disconnect": + wx.CallAfter(self.disconnect) + return True + return False + + def recvcb(self, l): + l = l.rstrip() + if not self.recvcb_actions(l): + report_type = self.recvcb_report(l) + isreport = report_type != REPORT_NONE + if report_type & REPORT_POS: + self.update_pos() + elif report_type & REPORT_TEMP: + wx.CallAfter(self.tempdisp.SetLabel, self.tempreadings.strip().replace("ok ", "")) + self.update_tempdisplay() + if not self.p.loud and (l not in ["ok", "wait"] and (not isreport or report_type & REPORT_MANUAL)): + wx.CallAfter(self.addtexttolog, l + "\n") + for listener in self.recvlisteners: + listener(l) + + def listfiles(self, line, ignored = False): + if "Begin file list" in line: + self.sdlisting = True + elif "End file list" in line: + self.sdlisting = False + self.recvlisteners.remove(self.listfiles) + wx.CallAfter(self.filesloaded) + elif self.sdlisting: + self.sdfiles.append(line.strip().lower()) + + def waitforsdresponse(self, l): + if "file.open failed" in l: + wx.CallAfter(self.statusbar.SetStatusText, _("Opening file failed.")) + self.recvlisteners.remove(self.waitforsdresponse) + return + if "File opened" in l: + wx.CallAfter(self.statusbar.SetStatusText, l) + if "File selected" in l: + wx.CallAfter(self.statusbar.SetStatusText, _("Starting print")) + self.sdprinting = True + self.p.send_now("M24") + self.startcb() + return + if "Done printing file" in l: + wx.CallAfter(self.statusbar.SetStatusText, l) + self.sdprinting = False + self.recvlisteners.remove(self.waitforsdresponse) + self.endcb() + return + if "SD printing byte" in l: + # M27 handler + try: + resp = l.split() + vals = resp[-1].split("/") + self.percentdone = 100.0 * int(vals[0]) / int(vals[1]) + except: + pass + + # -------------------------------------------------------------- + # Custom buttons handling + # -------------------------------------------------------------- + + def cbuttons_reload(self): + allcbs = getattr(self, "custombuttons_widgets", []) + for button in allcbs: + self.cbuttonssizer.Detach(button) + button.Destroy() + self.custombuttons_widgets = [] + custombuttons = self.custombuttons[:] + [None] + for i, btndef in enumerate(custombuttons): + if btndef is None: + if i == len(custombuttons) - 1: + self.newbuttonbutton = b = wx.Button(self.centerpanel, -1, "+", size = (19, 18), style = wx.BU_EXACTFIT) + b.SetForegroundColour("#4444ff") + b.SetToolTip(wx.ToolTip(_("click to add new custom button"))) + b.Bind(wx.EVT_BUTTON, self.cbutton_edit) + else: + b = wx.StaticText(self.panel, -1, "") + else: + b = wx.Button(self.centerpanel, -1, btndef.label, style = wx.BU_EXACTFIT) + b.SetToolTip(wx.ToolTip(_("Execute command: ") + btndef.command)) + if btndef.background: + b.SetBackgroundColour(btndef.background) + rr, gg, bb = b.GetBackgroundColour().Get() + if 0.3 * rr + 0.59 * gg + 0.11 * bb < 60: + b.SetForegroundColour("#ffffff") + b.custombutton = i + b.properties = btndef + if btndef is not None: + b.Bind(wx.EVT_BUTTON, self.process_button) + b.Bind(wx.EVT_MOUSE_EVENTS, self.editbutton) + self.custombuttons_widgets.append(b) + if type(self.cbuttonssizer) == wx.GridBagSizer: + self.cbuttonssizer.Add(b, pos = (i // 4, i % 4), flag = wx.EXPAND) + else: + self.cbuttonssizer.Add(b, flag = wx.EXPAND) + self.centerpanel.Layout() + self.centerpanel.GetContainingSizer().Layout() + + def help_button(self): + self.log(_('Defines custom button. Usage: button "title" [/c "colour"] command')) + + def do_button(self, argstr): + def nextarg(rest): + rest = rest.lstrip() + if rest.startswith('"'): + return rest[1:].split('"', 1) + else: + return rest.split(None, 1) + # try: + num, argstr = nextarg(argstr) + num = int(num) + title, argstr = nextarg(argstr) + colour = None + try: + c1, c2 = nextarg(argstr) + if c1 == "/c": + colour, argstr = nextarg(c2) + except: + pass + command = argstr.strip() + if num < 0 or num >= 64: + self.log(_("Custom button number should be between 0 and 63")) + return + while num >= len(self.custombuttons): + self.custombuttons.append(None) + self.custombuttons[num] = SpecialButton(title, command) + if colour is not None: + self.custombuttons[num].background = colour + if not self.processing_rc: + self.cbuttons_reload() + + def cbutton_save(self, n, bdef, new_n = None): + if new_n is None: new_n = n + if bdef is None or bdef == "": + self.save_in_rc(("button %d" % n), '') + elif bdef.background: + colour = bdef.background + if type(colour) not in (str, unicode): + if type(colour) == tuple and tuple(map(type, colour)) == (int, int, int): + colour = map(lambda x: x % 256, colour) + colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) + else: + colour = wx.Colour(colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) + self.save_in_rc(("button %d" % n), 'button %d "%s" /c "%s" %s' % (new_n, bdef.label, colour, bdef.command)) + else: + self.save_in_rc(("button %d" % n), 'button %d "%s" %s' % (new_n, bdef.label, bdef.command)) + + def cbutton_edit(self, e, button = None): + bedit = ButtonEdit(self) + if button is not None: + n = button.custombutton + bedit.name.SetValue(button.properties.label) + bedit.command.SetValue(button.properties.command) + if button.properties.background: + colour = button.properties.background + if type(colour) not in (str, unicode): + if type(colour) == tuple and tuple(map(type, colour)) == (int, int, int): + colour = map(lambda x: x % 256, colour) + colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) + else: + colour = wx.Colour(colour).GetAsString(wx.C2S_NAME | wx.C2S_HTML_SYNTAX) + bedit.color.SetValue(colour) + else: + n = len(self.custombuttons) + while n > 0 and self.custombuttons[n - 1] is None: + n -= 1 + if bedit.ShowModal() == wx.ID_OK: + if n == len(self.custombuttons): + self.custombuttons.append(None) + self.custombuttons[n] = SpecialButton(bedit.name.GetValue().strip(), bedit.command.GetValue().strip(), custom = True) + if bedit.color.GetValue().strip() != "": + self.custombuttons[n].background = bedit.color.GetValue() + self.cbutton_save(n, self.custombuttons[n]) + wx.CallAfter(bedit.Destroy) + wx.CallAfter(self.cbuttons_reload) + + def cbutton_remove(self, e, button): + n = button.custombutton + self.cbutton_save(n, None) + del self.custombuttons[n] + for i in range(n, len(self.custombuttons)): + self.cbutton_save(i, self.custombuttons[i]) + wx.CallAfter(self.cbuttons_reload) + + def cbutton_order(self, e, button, dir): + n = button.custombutton + if dir < 0: + n = n - 1 + if n + 1 >= len(self.custombuttons): + self.custombuttons.append(None) # pad + # swap + self.custombuttons[n], self.custombuttons[n + 1] = self.custombuttons[n + 1], self.custombuttons[n] + self.cbutton_save(n, self.custombuttons[n]) + self.cbutton_save(n + 1, self.custombuttons[n + 1]) + wx.CallAfter(self.cbuttons_reload) + + def editbutton(self, e): + if e.IsCommandEvent() or e.ButtonUp(wx.MOUSE_BTN_RIGHT): + if e.IsCommandEvent(): + pos = (0, 0) + else: + pos = e.GetPosition() + popupmenu = wx.Menu() + obj = e.GetEventObject() + if hasattr(obj, "custombutton"): + item = popupmenu.Append(-1, _("Edit custom button '%s'") % e.GetEventObject().GetLabelText()) + self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_edit(e, button), item) + item = popupmenu.Append(-1, _("Move left <<")) + self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_order(e, button, -1), item) + if obj.custombutton == 0: item.Enable(False) + item = popupmenu.Append(-1, _("Move right >>")) + self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_order(e, button, 1), item) + if obj.custombutton == 63: item.Enable(False) + pos = self.panel.ScreenToClient(e.GetEventObject().ClientToScreen(pos)) + item = popupmenu.Append(-1, _("Remove custom button '%s'") % e.GetEventObject().GetLabelText()) + self.Bind(wx.EVT_MENU, lambda e, button = e.GetEventObject(): self.cbutton_remove(e, button), item) + else: + item = popupmenu.Append(-1, _("Add custom button")) + self.Bind(wx.EVT_MENU, self.cbutton_edit, item) + self.panel.PopupMenu(popupmenu, pos) + elif e.Dragging() and e.ButtonIsDown(wx.MOUSE_BTN_LEFT): + obj = e.GetEventObject() + scrpos = obj.ClientToScreen(e.GetPosition()) + if not hasattr(self, "dragpos"): + self.dragpos = scrpos + e.Skip() + return + else: + dx, dy = self.dragpos[0] - scrpos[0], self.dragpos[1] - scrpos[1] + if dx * dx + dy * dy < 5 * 5: # threshold to detect dragging for jittery mice + e.Skip() + return + if not hasattr(self, "dragging"): + # init dragging of the custom button + if hasattr(obj, "custombutton") and obj.properties is not None: + for b in self.custombuttons_widgets: + if b.properties is None: + b.Enable() + b.SetLabel("") + b.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) + b.SetForegroundColour("black") + b.SetSize(obj.GetSize()) + if self.toolbarsizer.GetItem(b) is not None: + self.toolbarsizer.SetItemMinSize(b, obj.GetSize()) + self.mainsizer.Layout() + self.dragging = wx.Button(self.panel, -1, obj.GetLabel(), style = wx.BU_EXACTFIT) + self.dragging.SetBackgroundColour(obj.GetBackgroundColour()) + self.dragging.SetForegroundColour(obj.GetForegroundColour()) + self.dragging.sourcebutton = obj + self.dragging.Raise() + self.dragging.Disable() + self.dragging.SetPosition(self.panel.ScreenToClient(scrpos)) + self.last_drag_dest = obj + self.dragging.label = obj.s_label = obj.GetLabel() + self.dragging.bgc = obj.s_bgc = obj.GetBackgroundColour() + self.dragging.fgc = obj.s_fgc = obj.GetForegroundColour() + else: + # dragging in progress + self.dragging.SetPosition(self.panel.ScreenToClient(scrpos)) + wx.CallAfter(self.dragging.Refresh) + dst = None + src = self.dragging.sourcebutton + drg = self.dragging + for b in self.custombuttons_widgets: + if b.GetScreenRect().Contains(scrpos): + dst = b + break + if dst is not self.last_drag_dest: + if self.last_drag_dest is not None: + self.last_drag_dest.SetBackgroundColour(self.last_drag_dest.s_bgc) + self.last_drag_dest.SetForegroundColour(self.last_drag_dest.s_fgc) + self.last_drag_dest.SetLabel(self.last_drag_dest.s_label) + if dst is not None and dst is not src: + dst.s_bgc = dst.GetBackgroundColour() + dst.s_fgc = dst.GetForegroundColour() + dst.s_label = dst.GetLabel() + src.SetBackgroundColour(dst.GetBackgroundColour()) + src.SetForegroundColour(dst.GetForegroundColour()) + src.SetLabel(dst.GetLabel()) + dst.SetBackgroundColour(drg.bgc) + dst.SetForegroundColour(drg.fgc) + dst.SetLabel(drg.label) + else: + src.SetBackgroundColour(drg.bgc) + src.SetForegroundColour(drg.fgc) + src.SetLabel(drg.label) + self.last_drag_dest = dst + elif hasattr(self, "dragging") and not e.ButtonIsDown(wx.MOUSE_BTN_LEFT): + # dragging finished + obj = e.GetEventObject() + scrpos = obj.ClientToScreen(e.GetPosition()) + dst = None + src = self.dragging.sourcebutton + drg = self.dragging + for b in self.custombuttons_widgets: + if b.GetScreenRect().Contains(scrpos): + dst = b + break + if dst is not None: + src_i = src.custombutton + dst_i = dst.custombutton + self.custombuttons[src_i], self.custombuttons[dst_i] = self.custombuttons[dst_i], self.custombuttons[src_i] + self.cbutton_save(src_i, self.custombuttons[src_i]) + self.cbutton_save(dst_i, self.custombuttons[dst_i]) + while self.custombuttons[-1] is None: + del self.custombuttons[-1] + wx.CallAfter(self.dragging.Destroy) + del self.dragging + wx.CallAfter(self.cbuttons_reload) + del self.last_drag_dest + del self.dragpos + else: + e.Skip() + + def process_button(self, e): + try: + if hasattr(e.GetEventObject(), "custombutton"): + if wx.GetKeyState(wx.WXK_CONTROL) or wx.GetKeyState(wx.WXK_ALT): + return self.editbutton(e) + self.cur_button = e.GetEventObject().custombutton + command = e.GetEventObject().properties.command + command = self.precmd(command) + self.onecmd(command) + self.cur_button = None + except: + self.log(_("Failed to handle button")) + self.cur_button = None + raise + + # -------------------------------------------------------------- + # Macros handling + # -------------------------------------------------------------- + + def start_macro(self, macro_name, old_macro_definition = ""): + if not self.processing_rc: + def cb(definition): + if len(definition.strip()) == 0: + if old_macro_definition != "": + dialog = wx.MessageDialog(self, _("Do you want to erase the macro?"), style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION) + if dialog.ShowModal() == wx.ID_YES: + self.delete_macro(macro_name) + return + self.log(_("Cancelled.")) + return + self.cur_macro_name = macro_name + self.cur_macro_def = definition + self.end_macro() + MacroEditor(macro_name, old_macro_definition, cb) + else: + pronsole.pronsole.start_macro(self, macro_name, old_macro_definition) + + def end_macro(self): + pronsole.pronsole.end_macro(self) + self.update_macros_menu() + + def delete_macro(self, macro_name): + pronsole.pronsole.delete_macro(self, macro_name) + self.update_macros_menu() + + def new_macro(self, e = None): + dialog = wx.Dialog(self, -1, _("Enter macro name"), size = (260, 85)) + panel = wx.Panel(dialog, -1) + vbox = wx.BoxSizer(wx.VERTICAL) + wx.StaticText(panel, -1, _("Macro name:"), (8, 14)) + dialog.namectrl = wx.TextCtrl(panel, -1, '', (110, 8), size = (130, 24), style = wx.TE_PROCESS_ENTER) + hbox = wx.BoxSizer(wx.HORIZONTAL) + okb = wx.Button(dialog, wx.ID_OK, _("Ok"), size = (60, 24)) + dialog.Bind(wx.EVT_TEXT_ENTER, lambda e: dialog.EndModal(wx.ID_OK), dialog.namectrl) + hbox.Add(okb) + hbox.Add(wx.Button(dialog, wx.ID_CANCEL, _("Cancel"), size = (60, 24))) + vbox.Add(panel) + vbox.Add(hbox, 1, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10) + dialog.SetSizer(vbox) + dialog.Centre() + macro = "" + if dialog.ShowModal() == wx.ID_OK: + macro = dialog.namectrl.GetValue() + if macro != "": + wx.CallAfter(self.edit_macro, macro) + dialog.Destroy() + return macro + + def edit_macro(self, macro): + if macro == "": return self.new_macro() + if macro in self.macros: + old_def = self.macros[macro] + elif len([c for c in macro.encode("ascii", "replace") if not c.isalnum() and c != "_"]): + self.log(_("Macro name may contain only ASCII alphanumeric symbols and underscores")) + return + elif hasattr(self.__class__, "do_" + macro): + self.log(_("Name '%s' is being used by built-in command") % macro) + return + else: + old_def = "" + self.start_macro(macro, old_def) + return macro + + def update_macros_menu(self): + if not hasattr(self, "macros_menu"): + return # too early, menu not yet built + try: + while True: + item = self.macros_menu.FindItemByPosition(1) + if item is None: break + self.macros_menu.DeleteItem(item) + except: + pass + for macro in self.macros.keys(): + self.Bind(wx.EVT_MENU, lambda x, m = macro: self.start_macro(m, self.macros[m]), self.macros_menu.Append(-1, macro)) + + # -------------------------------------------------------------- + # Slic3r integration + # -------------------------------------------------------------- + + def load_slic3r_configs(self, menus): + """List Slic3r configurations and create menu""" + # Hack to get correct path for Slic3r config + orig_appname = self.app.GetAppName() + self.app.SetAppName("Slic3r") + configpath = wx.StandardPaths.Get().GetUserDataDir() + self.app.SetAppName(orig_appname) + self.slic3r_configpath = configpath + configfile = os.path.join(configpath, "slic3r.ini") + config = self.read_slic3r_config(configfile) + self.slic3r_configs = {} + for cat in menus: + menu = menus[cat] + pattern = os.path.join(configpath, cat, "*.ini") + files = sorted(glob.glob(pattern)) + try: + preset = config.get("presets", cat) + self.slic3r_configs[cat] = preset + except: + preset = None + self.slic3r_configs[cat] = None + for f in files: + name = os.path.splitext(os.path.basename(f))[0] + item = menu.Append(-1, name, f, wx.ITEM_RADIO) + item.Check(os.path.basename(f) == preset) + self.Bind(wx.EVT_MENU, + lambda event, cat = cat, f = f: + self.set_slic3r_config(configfile, cat, f), item) + + def read_slic3r_config(self, configfile, parser = None): + """Helper to read a Slic3r configuration file""" + import ConfigParser + parser = ConfigParser.RawConfigParser() + + class add_header(object): + def __init__(self, f): + self.f = f + self.header = '[dummy]' + + def readline(self): + if self.header: + try: return self.header + finally: self.header = None + else: + return self.f.readline() + parser.readfp(add_header(open(configfile)), configfile) + return parser + + def set_slic3r_config(self, configfile, cat, file): + """Set new preset for a given category""" + self.slic3r_configs[cat] = file + if self.settings.slic3rupdate: + config = self.read_slic3r_config(configfile) + config.set("presets", cat, os.path.basename(file)) + f = StringIO.StringIO() + config.write(f) + data = f.getvalue() + f.close() + data = data.replace("[dummy]\n", "") + with open(configfile, "w") as f: + f.write(data) + +class PronterApp(wx.App): + + mainwindow = None + + def __init__(self, *args, **kwargs): + super(PronterApp, self).__init__(*args, **kwargs) + self.SetAppName("Pronterface") + self.mainwindow = PronterWindow(self) + self.mainwindow.Show() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/rpc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/rpc.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,78 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +from SimpleXMLRPCServer import SimpleXMLRPCServer +from threading import Thread +import socket +import logging + +from .utils import install_locale, parse_temperature_report +install_locale('pronterface') + +RPC_PORT = 7978 + +class ProntRPC(object): + + server = None + + def __init__(self, pronsole, port = RPC_PORT): + self.pronsole = pronsole + used_port = port + while True: + try: + self.server = SimpleXMLRPCServer(("localhost", used_port), + allow_none = True, + logRequests = False) + if used_port != port: + logging.warning(_("RPC server bound on non-default port %d") % used_port) + break + except socket.error as e: + if e.errno == 98: + used_port += 1 + continue + else: + raise + self.server.register_function(self.get_status, 'status') + self.thread = Thread(target = self.run_server) + self.thread.start() + + def run_server(self): + self.server.serve_forever() + + def shutdown(self): + self.server.shutdown() + self.thread.join() + + def get_status(self): + if self.pronsole.p.printing: + progress = 100 * float(self.pronsole.p.queueindex) / len(self.pronsole.p.mainqueue) + elif self.pronsole.sdprinting: + progress = self.pronsole.percentdone + else: progress = None + if self.pronsole.p.printing or self.pronsole.sdprinting: + eta = self.pronsole.get_eta() + else: + eta = None + if self.pronsole.tempreadings: + temps = parse_temperature_report(self.pronsole.tempreadings) + else: + temps = None + z = self.pronsole.curlayer + return {"filename": self.pronsole.filename, + "progress": progress, + "eta": eta, + "temps": temps, + "z": z, + } diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/serialWrapper.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/serialWrapper.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,49 @@ +# Serial wrapper around pyserial that adds support for custom baudrates (250000) +# on linux, when pyserial is < 2.7 + +# This code was copied from the pyserial 2.7 base code. +# Therefore, it follows the license used by pyserial which is the '3-clause BSD license' + +from serial import * +import sys + +if sys.platform.startswith('linux'): + import serial.serialposix + + try: + import pkg_resources + + old_version = float(pkg_resources.get_distribution("pyserial").version) < 2.7 + except: + old_version = True + + if old_version and not hasattr(serial.serialposix, "TCGETS2") and \ + hasattr(serial.serialposix, "set_special_baudrate"): + # Detected pyserial < 2.7 which doesn't support custom baudrates + # Replacing set_special_baudrate with updated function from pyserial 2.7 + + TCGETS2 = 0x802C542A + TCSETS2 = 0x402C542B + BOTHER = 0o010000 + + def set_special_baudrate(port, baudrate): + # right size is 44 on x86_64, allow for some growth + import array + buf = array.array('i', [0] * 64) + + try: + # get serial_struct + FCNTL.ioctl(port.fd, TCGETS2, buf) + # set custom speed + buf[2] &= ~TERMIOS.CBAUD + buf[2] |= BOTHER + buf[9] = buf[10] = baudrate + + # set serial_struct + res = FCNTL.ioctl(port.fd, TCSETS2, buf) + except IOError, e: + raise ValueError('Failed to set custom baud rate (%s): %s' % (baudrate, e)) + + # We need to change the function inside the serialposix module otherwise, it won't + # be called by the code within that module + serial.serialposix.set_special_baudrate = set_special_baudrate diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/settings.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/settings.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,369 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import logging +import traceback + +from functools import wraps + +from .utils import parse_build_dimensions + +def setting_add_tooltip(func): + @wraps(func) + def decorator(self, *args, **kwargs): + widget = func(self, *args, **kwargs) + helptxt = self.help or "" + sep, deftxt = "", "" + if len(helptxt): + sep = "\n" + if helptxt.find("\n") >= 0: + sep = "\n\n" + if self.default is not "": + deftxt = _("Default: ") + resethelp = _("(Control-doubleclick to reset to default value)") + if len(repr(self.default)) > 10: + deftxt += "\n " + repr(self.default).strip("'") + "\n" + resethelp + else: + deftxt += repr(self.default) + " " + resethelp + helptxt += sep + deftxt + if len(helptxt): + widget.SetToolTipString(helptxt) + return widget + return decorator + +class Setting(object): + + DEFAULT_GROUP = "Printer" + + hidden = False + + def __init__(self, name, default, label = None, help = None, group = None): + self.name = name + self.default = default + self._value = default + self.label = label + self.help = help + self.group = group if group else Setting.DEFAULT_GROUP + + def _get_value(self): + return self._value + + def _set_value(self, value): + raise NotImplementedError + value = property(_get_value, _set_value) + + def set_default(self, e): + import wx + if e.CmdDown() and e.ButtonDClick() and self.default is not "": + confirmation = wx.MessageDialog(None, _("Are you sure you want to reset the setting to the default value: {0!r} ?").format(self.default), _("Confirm set default"), wx.ICON_EXCLAMATION | wx.YES_NO | wx.NO_DEFAULT) + if confirmation.ShowModal() == wx.ID_YES: + self._set_value(self.default) + else: + e.Skip() + + @setting_add_tooltip + def get_label(self, parent): + import wx + widget = wx.StaticText(parent, -1, self.label or self.name) + widget.set_default = self.set_default + return widget + + @setting_add_tooltip + def get_widget(self, parent): + return self.get_specific_widget(parent) + + def get_specific_widget(self, parent): + raise NotImplementedError + + def update(self): + raise NotImplementedError + + def __str__(self): + return self.name + + def __repr__(self): + return self.name + +class HiddenSetting(Setting): + + hidden = True + + def _set_value(self, value): + self._value = value + value = property(Setting._get_value, _set_value) + +class wxSetting(Setting): + + widget = None + + def _set_value(self, value): + self._value = value + if self.widget: + self.widget.SetValue(value) + value = property(Setting._get_value, _set_value) + + def update(self): + self.value = self.widget.GetValue() + +class StringSetting(wxSetting): + + def get_specific_widget(self, parent): + import wx + self.widget = wx.TextCtrl(parent, -1, str(self.value)) + return self.widget + +class ComboSetting(wxSetting): + + def __init__(self, name, default, choices, label = None, help = None, group = None): + super(ComboSetting, self).__init__(name, default, label, help, group) + self.choices = choices + + def get_specific_widget(self, parent): + import wx + self.widget = wx.ComboBox(parent, -1, str(self.value), choices = self.choices, style = wx.CB_DROPDOWN) + return self.widget + +class SpinSetting(wxSetting): + + def __init__(self, name, default, min, max, label = None, help = None, group = None, increment = 0.1): + super(SpinSetting, self).__init__(name, default, label, help, group) + self.min = min + self.max = max + self.increment = increment + + def get_specific_widget(self, parent): + from wx.lib.agw.floatspin import FloatSpin + self.widget = FloatSpin(parent, -1, min_val = self.min, max_val = self.max, digits = 0) + self.widget.SetValue(self.value) + orig = self.widget.GetValue + self.widget.GetValue = lambda: int(orig()) + return self.widget + +class FloatSpinSetting(SpinSetting): + + def get_specific_widget(self, parent): + from wx.lib.agw.floatspin import FloatSpin + self.widget = FloatSpin(parent, -1, value = self.value, min_val = self.min, max_val = self.max, increment = self.increment, digits = 2) + return self.widget + +class BooleanSetting(wxSetting): + + def _get_value(self): + return bool(self._value) + + def _set_value(self, value): + self._value = value + if self.widget: + self.widget.SetValue(bool(value)) + + value = property(_get_value, _set_value) + + def get_specific_widget(self, parent): + import wx + self.widget = wx.CheckBox(parent, -1) + self.widget.SetValue(bool(self.value)) + return self.widget + +class StaticTextSetting(wxSetting): + + def __init__(self, name, label = " ", text = "", help = None, group = None): + super(StaticTextSetting, self).__init__(name, "", label, help, group) + self.text = text + + def update(self): + pass + + def _get_value(self): + return "" + + def _set_value(self, value): + pass + + def get_specific_widget(self, parent): + import wx + self.widget = wx.StaticText(parent, -1, self.text) + return self.widget + +class BuildDimensionsSetting(wxSetting): + + widgets = None + + def _set_value(self, value): + self._value = value + if self.widgets: + self._set_widgets_values(value) + value = property(wxSetting._get_value, _set_value) + + def _set_widgets_values(self, value): + build_dimensions_list = parse_build_dimensions(value) + for i in range(len(self.widgets)): + self.widgets[i].SetValue(build_dimensions_list[i]) + + def get_widget(self, parent): + from wx.lib.agw.floatspin import FloatSpin + import wx + build_dimensions = parse_build_dimensions(self.value) + self.widgets = [] + w = lambda val, m, M: self.widgets.append(FloatSpin(parent, -1, value = val, min_val = m, max_val = M, digits = 2)) + addlabel = lambda name, pos: self.widget.Add(wx.StaticText(parent, -1, name), pos = pos, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5) + addwidget = lambda *pos: self.widget.Add(self.widgets[-1], pos = pos, flag = wx.RIGHT, border = 5) + self.widget = wx.GridBagSizer() + addlabel(_("Width"), (0, 0)) + w(build_dimensions[0], 0, 2000) + addwidget(0, 1) + addlabel(_("Depth"), (0, 2)) + w(build_dimensions[1], 0, 2000) + addwidget(0, 3) + addlabel(_("Height"), (0, 4)) + w(build_dimensions[2], 0, 2000) + addwidget(0, 5) + addlabel(_("X offset"), (1, 0)) + w(build_dimensions[3], -2000, 2000) + addwidget(1, 1) + addlabel(_("Y offset"), (1, 2)) + w(build_dimensions[4], -2000, 2000) + addwidget(1, 3) + addlabel(_("Z offset"), (1, 4)) + w(build_dimensions[5], -2000, 2000) + addwidget(1, 5) + addlabel(_("X home pos."), (2, 0)) + w(build_dimensions[6], -2000, 2000) + self.widget.Add(self.widgets[-1], pos = (2, 1)) + addlabel(_("Y home pos."), (2, 2)) + w(build_dimensions[7], -2000, 2000) + self.widget.Add(self.widgets[-1], pos = (2, 3)) + addlabel(_("Z home pos."), (2, 4)) + w(build_dimensions[8], -2000, 2000) + self.widget.Add(self.widgets[-1], pos = (2, 5)) + return self.widget + + def update(self): + values = [float(w.GetValue()) for w in self.widgets] + self.value = "%.02fx%.02fx%.02f%+.02f%+.02f%+.02f%+.02f%+.02f%+.02f" % tuple(values) + +class Settings(object): + def __baudrate_list(self): return ["2400", "9600", "19200", "38400", "57600", "115200", "250000"] + + def __init__(self, root): + # defaults here. + # the initial value determines the type + self._add(StringSetting("port", "", _("Serial port"), _("Port used to communicate with printer"))) + self._add(ComboSetting("baudrate", 115200, self.__baudrate_list(), _("Baud rate"), _("Communications Speed"))) + self._add(BooleanSetting("tcp_streaming_mode", False, _("TCP streaming mode"), _("When using a TCP connection to the printer, the streaming mode will not wait for acks from the printer to send new commands. This will break things such as ETA prediction, but can result in smoother prints.")), root.update_tcp_streaming_mode) + self._add(BooleanSetting("rpc_server", True, _("RPC server"), _("Enable RPC server to allow remotely querying print status")), root.update_rpc_server) + self._add(BooleanSetting("dtr", True, _("DTR"), _("Disabling DTR would prevent Arduino (RAMPS) from resetting upon connection"), "Printer")) + self._add(SpinSetting("bedtemp_abs", 110, 0, 400, _("Bed temperature for ABS"), _("Heated Build Platform temp for ABS (deg C)"), "Printer")) + self._add(SpinSetting("bedtemp_pla", 60, 0, 400, _("Bed temperature for PLA"), _("Heated Build Platform temp for PLA (deg C)"), "Printer")) + self._add(SpinSetting("temperature_abs", 230, 0, 400, _("Extruder temperature for ABS"), _("Extruder temp for ABS (deg C)"), "Printer")) + self._add(SpinSetting("temperature_pla", 185, 0, 400, _("Extruder temperature for PLA"), _("Extruder temp for PLA (deg C)"), "Printer")) + self._add(SpinSetting("xy_feedrate", 3000, 0, 50000, _("X && Y manual feedrate"), _("Feedrate for Control Panel Moves in X and Y (mm/min)"), "Printer")) + self._add(SpinSetting("z_feedrate", 100, 0, 50000, _("Z manual feedrate"), _("Feedrate for Control Panel Moves in Z (mm/min)"), "Printer")) + self._add(SpinSetting("e_feedrate", 100, 0, 1000, _("E manual feedrate"), _("Feedrate for Control Panel Moves in Extrusions (mm/min)"), "Printer")) + self._add(StringSetting("slicecommand", "python skeinforge/skeinforge_application/skeinforge_utilities/skeinforge_craft.py $s", _("Slice command"), _("Slice command"), "External")) + self._add(StringSetting("sliceoptscommand", "python skeinforge/skeinforge_application/skeinforge.py", _("Slicer options command"), _("Slice settings command"), "External")) + self._add(StringSetting("start_command", "", _("Start command"), _("Executable to run when the print is started"), "External")) + self._add(StringSetting("final_command", "", _("Final command"), _("Executable to run when the print is finished"), "External")) + self._add(StringSetting("error_command", "", _("Error command"), _("Executable to run when an error occurs"), "External")) + self._add(StringSetting("log_path", "", _("Log path"), _("Path to the log file. An empty path will log to the console."), "UI")) + + self._add(HiddenSetting("project_offset_x", 0.0)) + self._add(HiddenSetting("project_offset_y", 0.0)) + self._add(HiddenSetting("project_interval", 2.0)) + self._add(HiddenSetting("project_pause", 2.5)) + self._add(HiddenSetting("project_scale", 1.0)) + self._add(HiddenSetting("project_x", 1024)) + self._add(HiddenSetting("project_y", 768)) + self._add(HiddenSetting("project_projected_x", 150.0)) + self._add(HiddenSetting("project_direction", "Top Down")) + self._add(HiddenSetting("project_overshoot", 3.0)) + self._add(HiddenSetting("project_z_axis_rate", 200)) + self._add(HiddenSetting("project_layer", 0.1)) + self._add(HiddenSetting("project_prelift_gcode", "")) + self._add(HiddenSetting("project_postlift_gcode", "")) + self._add(HiddenSetting("pause_between_prints", True)) + self._add(HiddenSetting("default_extrusion", 5.0)) + self._add(HiddenSetting("last_extrusion", 5.0)) + self._add(HiddenSetting("total_filament_used", 0.0)) + + _settings = [] + + def __setattr__(self, name, value): + if name.startswith("_"): + return object.__setattr__(self, name, value) + if isinstance(value, Setting): + if not value.hidden: + self._settings.append(value) + object.__setattr__(self, "_" + name, value) + elif hasattr(self, "_" + name): + getattr(self, "_" + name).value = value + else: + setattr(self, name, StringSetting(name = name, default = value)) + + def __getattr__(self, name): + if name.startswith("_"): + return object.__getattribute__(self, name) + return getattr(self, "_" + name).value + + def _add(self, setting, callback = None, validate = None, + alias = None, autocomplete_list = None): + setattr(self, setting.name, setting) + if callback: + setattr(self, "__" + setting.name + "_cb", callback) + if validate: + setattr(self, "__" + setting.name + "_validate", validate) + if alias: + setattr(self, "__" + setting.name + "_alias", alias) + if autocomplete_list: + setattr(self, "__" + setting.name + "_list", autocomplete_list) + + def _set(self, key, value): + try: + value = getattr(self, "__%s_alias" % key)()[value] + except KeyError: + pass + except AttributeError: + pass + try: + getattr(self, "__%s_validate" % key)(value) + except AttributeError: + pass + t = type(getattr(self, key)) + if t == bool and value == "False": setattr(self, key, False) + else: setattr(self, key, t(value)) + try: + cb = None + try: + cb = getattr(self, "__%s_cb" % key) + except AttributeError: + pass + if cb is not None: cb(key, value) + except: + logging.warning((_("Failed to run callback after setting \"%s\":") % key) + + "\n" + traceback.format_exc()) + return value + + def _tabcomplete(self, key): + try: + return getattr(self, "__%s_list" % key)() + except AttributeError: + pass + try: + return getattr(self, "__%s_alias" % key)().keys() + except AttributeError: + pass + return [] + + def _all_settings(self): + return self._settings diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/stlplater.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/stlplater.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,524 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import os + +# Set up Internationalization using gettext +# searching for installed locales on /usr/share; uses relative folder if not found (windows) +from .utils import install_locale +install_locale('pronterface') + +import wx +import time +import logging +import threading +import math +import sys +import re +import traceback +import subprocess +from copy import copy + +from printrun import stltool +from printrun.objectplater import make_plater, PlaterPanel + +glview = False +if "-nogl" not in sys.argv: + try: + from printrun import stlview + glview = True + except: + logging.warning("Could not load 3D viewer for plater:" + + "\n" + traceback.format_exc()) + + +def evalme(s): + return eval(s[s.find("(") + 1:s.find(")")]) + +def transformation_matrix(model): + matrix = stltool.I + if any(model.centeroffset): + matrix = model.translation_matrix(model.centeroffset).dot(matrix) + if model.rot: + matrix = model.rotation_matrix([0, 0, model.rot]).dot(matrix) + if any(model.offsets): + matrix = model.translation_matrix(model.offsets).dot(matrix) + return matrix + +class showstl(wx.Window): + def __init__(self, parent, size, pos): + wx.Window.__init__(self, parent, size = size, pos = pos) + self.i = 0 + self.parent = parent + self.previ = 0 + self.Bind(wx.EVT_MOUSEWHEEL, self.rot) + self.Bind(wx.EVT_MOUSE_EVENTS, self.move) + self.Bind(wx.EVT_PAINT, self.repaint) + self.Bind(wx.EVT_KEY_DOWN, self.keypress) + self.triggered = 0 + self.initpos = None + self.prevsel = -1 + + def prepare_model(self, m, scale): + m.bitmap = wx.EmptyBitmap(800, 800, 32) + dc = wx.MemoryDC() + dc.SelectObject(m.bitmap) + dc.SetBackground(wx.Brush((0, 0, 0, 0))) + dc.SetBrush(wx.Brush((0, 0, 0, 255))) + dc.SetBrush(wx.Brush(wx.Colour(128, 255, 128))) + dc.SetPen(wx.Pen(wx.Colour(128, 128, 128))) + for i in m.facets: + dc.DrawPolygon([wx.Point(400 + scale * p[0], (400 - scale * p[1])) for p in i[1]]) + dc.SelectObject(wx.NullBitmap) + m.bitmap.SetMask(wx.Mask(m.bitmap, wx.Colour(0, 0, 0, 255))) + + def move_shape(self, delta): + """moves shape (selected in l, which is list ListBox of shapes) + by an offset specified in tuple delta. + Positive numbers move to (rigt, down)""" + name = self.parent.l.GetSelection() + if name == wx.NOT_FOUND: + return False + name = self.parent.l.GetString(name) + model = self.parent.models[name] + model.offsets = [model.offsets[0] + delta[0], + model.offsets[1] + delta[1], + model.offsets[2] + ] + self.Refresh() + return True + + def move(self, event): + if event.ButtonUp(wx.MOUSE_BTN_LEFT): + if self.initpos is not None: + currentpos = event.GetPositionTuple() + delta = (0.5 * (currentpos[0] - self.initpos[0]), + -0.5 * (currentpos[1] - self.initpos[1]) + ) + self.move_shape(delta) + self.Refresh() + self.initpos = None + elif event.ButtonDown(wx.MOUSE_BTN_RIGHT): + self.parent.right(event) + elif event.Dragging(): + if self.initpos is None: + self.initpos = event.GetPositionTuple() + self.Refresh() + dc = wx.ClientDC(self) + p = event.GetPositionTuple() + dc.DrawLine(self.initpos[0], self.initpos[1], p[0], p[1]) + del dc + else: + event.Skip() + + def rotate_shape(self, angle): + """rotates acive shape + positive angle is clockwise + """ + self.i += angle + if not self.triggered: + self.triggered = 1 + threading.Thread(target = self.cr).start() + + def keypress(self, event): + """gets keypress events and moves/rotates acive shape""" + keycode = event.GetKeyCode() + step = 5 + angle = 18 + if event.ControlDown(): + step = 1 + angle = 1 + # h + if keycode == 72: + self.move_shape((-step, 0)) + # l + if keycode == 76: + self.move_shape((step, 0)) + # j + if keycode == 75: + self.move_shape((0, step)) + # k + if keycode == 74: + self.move_shape((0, -step)) + # [ + if keycode == 91: + self.rotate_shape(-angle) + # ] + if keycode == 93: + self.rotate_shape(angle) + event.Skip() + + def rotateafter(self): + if self.i != self.previ: + i = self.parent.l.GetSelection() + if i != wx.NOT_FOUND: + self.parent.models[self.parent.l.GetString(i)].rot -= 5 * (self.i - self.previ) + self.previ = self.i + self.Refresh() + + def cr(self): + time.sleep(0.01) + wx.CallAfter(self.rotateafter) + self.triggered = 0 + + def rot(self, event): + z = event.GetWheelRotation() + s = self.parent.l.GetSelection() + if self.prevsel != s: + self.i = 0 + self.prevsel = s + if z < 0: + self.rotate_shape(-1) + else: + self.rotate_shape(1) + + def repaint(self, event): + dc = wx.PaintDC(self) + self.paint(dc = dc) + + def paint(self, coord1 = "x", coord2 = "y", dc = None): + if dc is None: + dc = wx.ClientDC(self) + scale = 2 + dc.SetPen(wx.Pen(wx.Colour(100, 100, 100))) + for i in xrange(20): + dc.DrawLine(0, i * scale * 10, 400, i * scale * 10) + dc.DrawLine(i * scale * 10, 0, i * scale * 10, 400) + dc.SetPen(wx.Pen(wx.Colour(0, 0, 0))) + for i in xrange(4): + dc.DrawLine(0, i * scale * 50, 400, i * scale * 50) + dc.DrawLine(i * scale * 50, 0, i * scale * 50, 400) + dc.SetBrush(wx.Brush(wx.Colour(128, 255, 128))) + dc.SetPen(wx.Pen(wx.Colour(128, 128, 128))) + dcs = wx.MemoryDC() + for m in self.parent.models.values(): + b = m.bitmap + im = b.ConvertToImage() + imgc = wx.Point(im.GetWidth() / 2, im.GetHeight() / 2) + im = im.Rotate(math.radians(m.rot), imgc, 0) + bm = wx.BitmapFromImage(im) + dcs.SelectObject(bm) + bsz = bm.GetSize() + dc.Blit(scale * m.offsets[0] - bsz[0] / 2, 400 - (scale * m.offsets[1] + bsz[1] / 2), bsz[0], bsz[1], dcs, 0, 0, useMask = 1) + del dc + +class StlPlaterPanel(PlaterPanel): + + load_wildcard = _("STL files (*.stl;*.STL)|*.stl;*.STL|OpenSCAD files (*.scad)|*.scad") + save_wildcard = _("STL files (*.stl;*.STL)|*.stl;*.STL") + + def prepare_ui(self, filenames = [], callback = None, + parent = None, build_dimensions = None, circular_platform = False, + simarrange_path = None, antialias_samples = 0): + super(StlPlaterPanel, self).prepare_ui(filenames, callback, parent, build_dimensions) + self.cutting = False + self.cutting_axis = None + self.cutting_dist = None + if glview: + viewer = stlview.StlViewPanel(self, (580, 580), + build_dimensions = self.build_dimensions, + circular = circular_platform, + antialias_samples = antialias_samples) + # Cutting tool + nrows = self.menusizer.GetRows() + self.menusizer.Add(wx.StaticText(self.menupanel, -1, _("Cut along:")), + pos = (nrows, 0), span = (1, 1), flag = wx.ALIGN_CENTER) + cutconfirmbutton = wx.Button(self.menupanel, label = _("Confirm cut")) + cutconfirmbutton.Bind(wx.EVT_BUTTON, self.cut_confirm) + cutconfirmbutton.Disable() + self.cutconfirmbutton = cutconfirmbutton + self.menusizer.Add(cutconfirmbutton, pos = (nrows, 1), span = (1, 1), flag = wx.EXPAND) + cutpanel = wx.Panel(self.menupanel, -1) + cutsizer = self.cutsizer = wx.BoxSizer(wx.HORIZONTAL) + cutpanel.SetSizer(cutsizer) + cutxplusbutton = wx.ToggleButton(cutpanel, label = _(">X"), style = wx.BU_EXACTFIT) + cutxplusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "x", 1)) + cutsizer.Add(cutxplusbutton, 1, flag = wx.EXPAND) + cutzplusbutton = wx.ToggleButton(cutpanel, label = _(">Y"), style = wx.BU_EXACTFIT) + cutzplusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "y", 1)) + cutsizer.Add(cutzplusbutton, 1, flag = wx.EXPAND) + cutzplusbutton = wx.ToggleButton(cutpanel, label = _(">Z"), style = wx.BU_EXACTFIT) + cutzplusbutton.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.start_cutting_tool(event, "z", 1)) + cutsizer.Add(cutzplusbutton, 1, flag = wx.EXPAND) + cutxminusbutton = wx.ToggleButton(cutpanel, label = _(" 0: + logging.error(_("Plate full, please remove some objects")) + break + if "File:" in line: + bits = pos_regexp.match(line).groups() + filename = bits[0] + x = float(bits[1]) + y = float(bits[2]) + rot = -float(bits[3]) + for name, model in models.items(): + # FIXME: not sure this is going to work superwell with utf8 + if model.filename == filename: + model.offsets[0] = x + self.build_dimensions[3] + model.offsets[1] = y + self.build_dimensions[4] + model.rot = rot + del models[name] + break + if p.wait() != 0: + raise RuntimeError(_("simarrange failed")) + +StlPlater = make_plater(StlPlaterPanel) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/stltool.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/stltool.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,390 @@ +# coding: utf-8 + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import sys +import struct +import math +import logging + +import numpy +import numpy.linalg + +def normalize(v): + return v / numpy.linalg.norm(v) + +def genfacet(v): + veca = v[1] - v[0] + vecb = v[2] - v[1] + vecx = numpy.cross(veca, vecb) + vlen = numpy.linalg.norm(vecx) + if vlen == 0: + vlen = 1 + normal = vecx / vlen + return (normal, v) + +I = numpy.identity(4) + +def homogeneous(v, w = 1): + return numpy.append(v, w) + +def applymatrix(facet, matrix = I): + return genfacet(map(lambda x: matrix.dot(homogeneous(x))[:3], facet[1])) + +def ray_triangle_intersection(ray_near, ray_dir, (v1, v2, v3)): + """ + Möller–Trumbore intersection algorithm in pure python + Based on http://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm + """ + eps = 0.000001 + edge1 = v2 - v1 + edge2 = v3 - v1 + pvec = numpy.cross(ray_dir, edge2) + det = edge1.dot(pvec) + if abs(det) < eps: + return False, None + inv_det = 1. / det + tvec = ray_near - v1 + u = tvec.dot(pvec) * inv_det + if u < 0. or u > 1.: + return False, None + qvec = numpy.cross(tvec, edge1) + v = ray_dir.dot(qvec) * inv_det + if v < 0. or u + v > 1.: + return False, None + + t = edge2.dot(qvec) * inv_det + if t < eps: + return False, None + + return True, t + +def ray_rectangle_intersection(ray_near, ray_dir, p0, p1, p2, p3): + match1, _ = ray_triangle_intersection(ray_near, ray_dir, (p0, p1, p2)) + match2, _ = ray_triangle_intersection(ray_near, ray_dir, (p0, p2, p3)) + return match1 or match2 + +def ray_box_intersection(ray_near, ray_dir, p0, p1): + x0, y0, z0 = p0[:] + x1, y1, z1 = p1[:] + rectangles = [((x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0)), + ((x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1)), + ((x0, y0, z0), (x1, y0, z0), (x1, y0, z1), (x0, y0, z1)), + ((x0, y1, z0), (x1, y1, z0), (x1, y1, z1), (x0, y1, z1)), + ((x0, y0, z0), (x0, y1, z0), (x0, y1, z1), (x0, y0, z1)), + ((x1, y0, z0), (x1, y1, z0), (x1, y1, z1), (x1, y0, z1)), + ] + rectangles = [(numpy.array(p) for p in rect) + for rect in rectangles] + for rect in rectangles: + if ray_rectangle_intersection(ray_near, ray_dir, *rect): + return True + return False + +def emitstl(filename, facets = [], objname = "stltool_export", binary = True): + if filename is None: + return + if binary: + with open(filename, "wb") as f: + buf = "".join(["\0"] * 80) + buf += struct.pack(" maxx: + maxx = vert[0] + if vert[1] > maxy: + maxy = vert[1] + if vert[2] > maxz: + maxz = vert[2] + self._dims = [minx, maxx, miny, maxy, minz, maxz] + return self._dims + dims = property(_get_dims) + + def __init__(self, filename = None): + self.facet = (numpy.zeros(3), (numpy.zeros(3), numpy.zeros(3), numpy.zeros(3))) + self.facets = [] + self.facetsminz = [] + self.facetsmaxz = [] + + self.name = "" + self.insolid = 0 + self.infacet = 0 + self.inloop = 0 + self.facetloc = 0 + if filename is None: + return + with open(filename) as f: + data = f.read() + if "facet normal" in data[1:300] and "outer loop" in data[1:300]: + lines = data.split("\n") + for line in lines: + if not self.parseline(line): + return + else: + logging.warning("Not an ascii stl solid - attempting to parse as binary") + f = open(filename, "rb") + buf = f.read(84) + while len(buf) < 84: + newdata = f.read(84 - len(buf)) + if not len(newdata): + break + buf += newdata + facetcount = struct.unpack_from(" 0: + e2 = - e2 + e3 = - e3 + matrix = [[e1[0], e2[0], e3[0], 0], + [e1[1], e2[1], e3[1], 0], + [e1[2], e2[2], e3[2], 0], + [0, 0, 0, 1]] + matrix = numpy.array(matrix) + # Inverse change of basis matrix + matrix = numpy.linalg.inv(matrix) + # Set first vertex of facet as origin + neworig = matrix.dot(homogeneous(facet[0])) + matrix[:3, 3] = -neworig[:3] + newmodel = self.transform(matrix) + return newmodel + + def cut(self, axis, direction, dist): + s = stl() + s.facets = [] + f = min if direction == 1 else max + for _, facet in self.facets: + minval = f([vertex[axis] for vertex in facet]) + if direction * minval > direction * dist: + continue + vertices = [] + for vertex in facet: + vertex = numpy.copy(vertex) + if direction * (vertex[axis] - dist) > 0: + vertex[axis] = dist + vertices.append(vertex) + s.facets.append(genfacet(vertices)) + s.insolid = 0 + s.infacet = 0 + s.inloop = 0 + s.facetloc = 0 + s.name = self.name + for facet in s.facets: + s.facetsminz += [(min(map(lambda x:x[2], facet[1])), facet)] + s.facetsmaxz += [(max(map(lambda x:x[2], facet[1])), facet)] + return s + + def translation_matrix(self, v): + matrix = [[1, 0, 0, v[0]], + [0, 1, 0, v[1]], + [0, 0, 1, v[2]], + [0, 0, 0, 1] + ] + return numpy.array(matrix) + + def translate(self, v = [0, 0, 0]): + return self.transform(self.translation_matrix(v)) + + def rotation_matrix(self, v): + z = v[2] + matrix1 = [[math.cos(math.radians(z)), -math.sin(math.radians(z)), 0, 0], + [math.sin(math.radians(z)), math.cos(math.radians(z)), 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + matrix1 = numpy.array(matrix1) + y = v[0] + matrix2 = [[1, 0, 0, 0], + [0, math.cos(math.radians(y)), -math.sin(math.radians(y)), 0], + [0, math.sin(math.radians(y)), math.cos(math.radians(y)), 0], + [0, 0, 0, 1] + ] + matrix2 = numpy.array(matrix2) + x = v[1] + matrix3 = [[math.cos(math.radians(x)), 0, -math.sin(math.radians(x)), 0], + [0, 1, 0, 0], + [math.sin(math.radians(x)), 0, math.cos(math.radians(x)), 0], + [0, 0, 0, 1] + ] + matrix3 = numpy.array(matrix3) + return matrix3.dot(matrix2.dot(matrix1)) + + def rotate(self, v = [0, 0, 0]): + return self.transform(self.rotation_matrix(v)) + + def scale_matrix(self, v): + matrix = [[v[0], 0, 0, 0], + [0, v[1], 0, 0], + [0, 0, v[2], 0], + [0, 0, 0, 1] + ] + return numpy.array(matrix) + + def scale(self, v = [0, 0, 0]): + return self.transform(self.scale_matrix(v)) + + def transform(self, m = I): + s = stl() + s.facets = [applymatrix(i, m) for i in self.facets] + s.insolid = 0 + s.infacet = 0 + s.inloop = 0 + s.facetloc = 0 + s.name = self.name + for facet in s.facets: + s.facetsminz += [(min(map(lambda x:x[2], facet[1])), facet)] + s.facetsmaxz += [(max(map(lambda x:x[2], facet[1])), facet)] + return s + + def export(self, f = sys.stdout): + f.write("solid " + self.name + "\n") + for i in self.facets: + f.write(" facet normal " + " ".join(map(str, i[0])) + "\n") + f.write(" outer loop" + "\n") + for j in i[1]: + f.write(" vertex " + " ".join(map(str, j)) + "\n") + f.write(" endloop" + "\n") + f.write(" endfacet" + "\n") + f.write("endsolid " + self.name + "\n") + f.flush() + + def parseline(self, l): + l = l.strip() + if l.startswith("solid"): + self.insolid = 1 + self.name = l[6:] + elif l.startswith("endsolid"): + self.insolid = 0 + return 0 + elif l.startswith("facet normal"): + l = l.replace(", ", ".") + self.infacet = 1 + self.facetloc = 0 + normal = numpy.array(map(float, l.split()[2:])) + self.facet = (normal, (numpy.zeros(3), numpy.zeros(3), numpy.zeros(3))) + elif l.startswith("endfacet"): + self.infacet = 0 + self.facets.append(self.facet) + facet = self.facet + self.facetsminz += [(min(map(lambda x:x[2], facet[1])), facet)] + self.facetsmaxz += [(max(map(lambda x:x[2], facet[1])), facet)] + elif l.startswith("vertex"): + l = l.replace(", ", ".") + self.facet[1][self.facetloc][:] = numpy.array(map(float, l.split()[1:])) + self.facetloc += 1 + return 1 + +if __name__ == "__main__": + s = stl("../../Downloads/frame-vertex-neo-foot-x4.stl") + for i in xrange(11, 11): + working = s.facets[:] + for j in reversed(sorted(s.facetsminz)): + if j[0] > i: + working.remove(j[1]) + else: + break + for j in (sorted(s.facetsmaxz)): + if j[0] < i: + working.remove(j[1]) + else: + break + + print i, len(working) + emitstl("../../Downloads/frame-vertex-neo-foot-x4-a.stl", s.facets, "emitted_object") +# stl("../prusamendel/stl/mendelplate.stl") diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/stlview.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/stlview.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,457 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx +import time + +import numpy +import pyglet +pyglet.options['debug_gl'] = True + +from pyglet.gl import GL_AMBIENT_AND_DIFFUSE, glBegin, glClearColor, \ + glColor3f, GL_CULL_FACE, GL_DEPTH_TEST, GL_DIFFUSE, GL_EMISSION, \ + glEnable, glEnd, GL_FILL, GLfloat, GL_FRONT_AND_BACK, GL_LIGHT0, \ + GL_LIGHT1, glLightfv, GL_LIGHTING, GL_LINE, glMaterialf, glMaterialfv, \ + glMultMatrixd, glNormal3f, glPolygonMode, glPopMatrix, GL_POSITION, \ + glPushMatrix, glRotatef, glScalef, glShadeModel, GL_SHININESS, \ + GL_SMOOTH, GL_SPECULAR, glTranslatef, GL_TRIANGLES, glVertex3f, \ + glGetDoublev, GL_MODELVIEW_MATRIX, GLdouble, glClearDepth, glDepthFunc, \ + GL_LEQUAL, GL_BLEND, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, \ + GL_LINE_LOOP, glGetFloatv, GL_LINE_WIDTH, glLineWidth, glDisable, \ + GL_LINE_SMOOTH +from pyglet import gl + +from .gl.panel import wxGLPanel +from .gl.trackball import build_rotmatrix +from .gl.libtatlin import actors + +def vec(*args): + return (GLfloat * len(args))(*args) + +class stlview(object): + def __init__(self, facets, batch): + # Create the vertex and normal arrays. + vertices = [] + normals = [] + + for i in facets: + for j in i[1]: + vertices.extend(j) + normals.extend(i[0]) + + # Create a list of triangle indices. + indices = range(3 * len(facets)) # [[3*i, 3*i+1, 3*i+2] for i in xrange(len(facets))] + self.vertex_list = batch.add_indexed(len(vertices) // 3, + GL_TRIANGLES, + None, # group, + indices, + ('v3f/static', vertices), + ('n3f/static', normals)) + + def delete(self): + self.vertex_list.delete() + +class StlViewPanel(wxGLPanel): + + do_lights = False + + def __init__(self, parent, size, id = wx.ID_ANY, + build_dimensions = None, circular = False, + antialias_samples = 0): + super(StlViewPanel, self).__init__(parent, id, wx.DefaultPosition, size, 0, + antialias_samples = antialias_samples) + self.batches = [] + self.rot = 0 + self.canvas.Bind(wx.EVT_MOUSE_EVENTS, self.move) + self.canvas.Bind(wx.EVT_MOUSEWHEEL, self.wheel) + self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.double_click) + self.initialized = True + self.parent = parent + self.initpos = None + if build_dimensions: + self.build_dimensions = build_dimensions + else: + self.build_dimensions = [200, 200, 100, 0, 0, 0] + self.platform = actors.Platform(self.build_dimensions, + circular = circular) + self.dist = max(self.build_dimensions[0], self.build_dimensions[1]) + self.basequat = [0, 0, 0, 1] + wx.CallAfter(self.forceresize) + self.mousepos = (0, 0) + + def OnReshape(self): + self.mview_initialized = False + super(StlViewPanel, self).OnReshape() + + # ========================================================================== + # GLFrame OpenGL Event Handlers + # ========================================================================== + def OnInitGL(self, call_reshape = True): + '''Initialize OpenGL for use in the window.''' + if self.GLinitialized: + return + self.GLinitialized = True + # create a pyglet context for this panel + self.pygletcontext = gl.Context(gl.current_context) + self.pygletcontext.canvas = self + self.pygletcontext.set_current() + # normal gl init + glClearColor(0, 0, 0, 1) + glColor3f(1, 0, 0) + glEnable(GL_DEPTH_TEST) + glClearDepth(1.0) + glDepthFunc(GL_LEQUAL) + glEnable(GL_CULL_FACE) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + # Uncomment this line for a wireframe view + # glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) + + # Simple light setup. On Windows GL_LIGHT0 is enabled by default, + # but this is not the case on Linux or Mac, so remember to always + # include it. + glEnable(GL_LIGHTING) + glEnable(GL_LIGHT0) + glEnable(GL_LIGHT1) + + glLightfv(GL_LIGHT0, GL_POSITION, vec(.5, .5, 1, 0)) + glLightfv(GL_LIGHT0, GL_SPECULAR, vec(.5, .5, 1, 1)) + glLightfv(GL_LIGHT0, GL_DIFFUSE, vec(1, 1, 1, 1)) + glLightfv(GL_LIGHT1, GL_POSITION, vec(1, 0, .5, 0)) + glLightfv(GL_LIGHT1, GL_DIFFUSE, vec(.5, .5, .5, 1)) + glLightfv(GL_LIGHT1, GL_SPECULAR, vec(1, 1, 1, 1)) + glShadeModel(GL_SMOOTH) + + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vec(0.5, 0, 0.3, 1)) + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, vec(1, 1, 1, 1)) + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50) + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, vec(0, 0.1, 0, 0.9)) + if call_reshape: + self.OnReshape() + if hasattr(self.parent, "filenames") and self.parent.filenames: + for filename in self.parent.filenames: + self.parent.load_file(filename) + self.parent.autoplate() + if hasattr(self.parent, "loadcb"): + self.parent.loadcb() + self.parent.filenames = None + + def double_click(self, event): + if hasattr(self.parent, "clickcb") and self.parent.clickcb: + self.parent.clickcb(event) + + def forceresize(self): + self.SetClientSize((self.GetClientSize()[0], self.GetClientSize()[1] + 1)) + self.SetClientSize((self.GetClientSize()[0], self.GetClientSize()[1] - 1)) + self.initialized = False + + def move(self, event): + """react to mouse actions: + no mouse: show red mousedrop + LMB: move active object, + with shift rotate viewport + RMB: nothing + with shift move viewport + """ + self.mousepos = event.GetPositionTuple() + if event.Dragging() and event.LeftIsDown(): + self.handle_rotation(event) + elif event.Dragging() and event.RightIsDown(): + self.handle_translation(event) + elif event.ButtonUp(wx.MOUSE_BTN_LEFT): + if self.initpos is not None: + self.initpos = None + elif event.ButtonUp(wx.MOUSE_BTN_RIGHT): + if self.initpos is not None: + self.initpos = None + else: + event.Skip() + return + event.Skip() + wx.CallAfter(self.Refresh) + + def handle_wheel(self, event): + delta = event.GetWheelRotation() + factor = 1.05 + x, y = event.GetPositionTuple() + x, y, _ = self.mouse_to_3d(x, y, local_transform = True) + if delta > 0: + self.zoom(factor, (x, y)) + else: + self.zoom(1 / factor, (x, y)) + + def wheel(self, event): + """react to mouse wheel actions: + rotate object + with shift zoom viewport + """ + self.handle_wheel(event) + wx.CallAfter(self.Refresh) + + def keypress(self, event): + """gets keypress events and moves/rotates acive shape""" + keycode = event.GetKeyCode() + step = 5 + angle = 18 + if event.ControlDown(): + step = 1 + angle = 1 + # h + if keycode == 72: + self.parent.move_shape((-step, 0)) + # l + if keycode == 76: + self.parent.move_shape((step, 0)) + # j + if keycode == 75: + self.parent.move_shape((0, step)) + # k + if keycode == 74: + self.parent.move_shape((0, -step)) + # [ + if keycode == 91: + self.parent.rotate_shape(-angle) + # ] + if keycode == 93: + self.parent.rotate_shape(angle) + event.Skip() + wx.CallAfter(self.Refresh) + + def anim(self, obj): + g = 50 * 9.8 + v = 20 + dt = 0.05 + basepos = obj.offsets[2] + obj.offsets[2] += obj.animoffset + while obj.offsets[2] > -1: + time.sleep(dt) + obj.offsets[2] -= v * dt + v += g * dt + if obj.offsets[2] < 0: + obj.scale[2] *= 1 - 3 * dt + # return + v = v / 4 + while obj.offsets[2] < basepos: + time.sleep(dt) + obj.offsets[2] += v * dt + v -= g * dt + obj.scale[2] *= 1 + 5 * dt + obj.scale[2] = 1.0 + + def create_objects(self): + '''create opengl objects when opengl is initialized''' + if not self.platform.initialized: + self.platform.init() + self.initialized = 1 + wx.CallAfter(self.Refresh) + + def prepare_model(self, m, scale): + batch = pyglet.graphics.Batch() + stlview(m.facets, batch = batch) + m.batch = batch + # m.animoffset = 300 + # threading.Thread(target = self.anim, args = (m, )).start() + wx.CallAfter(self.Refresh) + + def update_object_resize(self): + '''called when the window recieves only if opengl is initialized''' + pass + + def draw_objects(self): + '''called in the middle of ondraw after the buffer has been cleared''' + self.create_objects() + + glPushMatrix() + glTranslatef(0, 0, -self.dist) + glMultMatrixd(build_rotmatrix(self.basequat)) # Rotate according to trackball + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vec(0.2, 0.2, 0.2, 1)) + glTranslatef(- self.build_dimensions[3] - self.platform.width / 2, + - self.build_dimensions[4] - self.platform.depth / 2, 0) # Move origin to bottom left of platform + # Draw platform + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) + glDisable(GL_LIGHTING) + self.platform.draw() + glEnable(GL_LIGHTING) + # Draw mouse + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) + inter = self.mouse_to_plane(self.mousepos[0], self.mousepos[1], + plane_normal = (0, 0, 1), plane_offset = 0, + local_transform = False) + if inter is not None: + glPushMatrix() + glTranslatef(inter[0], inter[1], inter[2]) + glBegin(GL_TRIANGLES) + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vec(1, 0, 0, 1)) + glNormal3f(0, 0, 1) + glVertex3f(2, 2, 0) + glVertex3f(-2, 2, 0) + glVertex3f(-2, -2, 0) + glVertex3f(2, -2, 0) + glVertex3f(2, 2, 0) + glVertex3f(-2, -2, 0) + glEnd() + glPopMatrix() + + # Draw objects + glDisable(GL_CULL_FACE) + glPushMatrix() + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vec(0.3, 0.7, 0.5, 1)) + for i in self.parent.models: + model = self.parent.models[i] + glPushMatrix() + glTranslatef(*(model.offsets)) + glRotatef(model.rot, 0.0, 0.0, 1.0) + glTranslatef(*(model.centeroffset)) + glScalef(*model.scale) + model.batch.draw() + glPopMatrix() + glPopMatrix() + glEnable(GL_CULL_FACE) + + # Draw cutting plane + if self.parent.cutting: + # FIXME: make this a proper Actor + axis = self.parent.cutting_axis + fixed_dist = self.parent.cutting_dist + dist, plane_width, plane_height = self.get_cutting_plane(axis, fixed_dist) + if dist is not None: + glPushMatrix() + if axis == "x": + glRotatef(90, 0, 1, 0) + glRotatef(90, 0, 0, 1) + glTranslatef(0, 0, dist) + elif axis == "y": + glRotatef(90, 1, 0, 0) + glTranslatef(0, 0, -dist) + elif axis == "z": + glTranslatef(0, 0, dist) + glDisable(GL_CULL_FACE) + glBegin(GL_TRIANGLES) + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vec(0, 0.9, 0.15, 0.3)) + glNormal3f(0, 0, self.parent.cutting_direction) + glVertex3f(plane_width, plane_height, 0) + glVertex3f(0, plane_height, 0) + glVertex3f(0, 0, 0) + glVertex3f(plane_width, 0, 0) + glVertex3f(plane_width, plane_height, 0) + glVertex3f(0, 0, 0) + glEnd() + glEnable(GL_CULL_FACE) + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) + glEnable(GL_LINE_SMOOTH) + orig_linewidth = (GLfloat)() + glGetFloatv(GL_LINE_WIDTH, orig_linewidth) + glLineWidth(4.0) + glBegin(GL_LINE_LOOP) + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vec(0, 0.8, 0.15, 1)) + glVertex3f(0, 0, 0) + glVertex3f(0, plane_height, 0) + glVertex3f(plane_width, plane_height, 0) + glVertex3f(plane_width, 0, 0) + glEnd() + glLineWidth(orig_linewidth) + glDisable(GL_LINE_SMOOTH) + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) + glPopMatrix() + + glPopMatrix() + + # ========================================================================== + # Utils + # ========================================================================== + def get_modelview_mat(self, local_transform): + mvmat = (GLdouble * 16)() + if local_transform: + glPushMatrix() + # Rotate according to trackball + glTranslatef(0, 0, -self.dist) + glMultMatrixd(build_rotmatrix(self.basequat)) # Rotate according to trackball + glTranslatef(- self.build_dimensions[3] - self.platform.width / 2, + - self.build_dimensions[4] - self.platform.depth / 2, 0) # Move origin to bottom left of platform + glGetDoublev(GL_MODELVIEW_MATRIX, mvmat) + glPopMatrix() + else: + glGetDoublev(GL_MODELVIEW_MATRIX, mvmat) + return mvmat + + def get_cutting_plane(self, cutting_axis, fixed_dist, local_transform = False): + cutting_plane_sizes = {"x": (self.platform.depth, self.platform.height), + "y": (self.platform.width, self.platform.height), + "z": (self.platform.width, self.platform.depth)} + plane_width, plane_height = cutting_plane_sizes[cutting_axis] + if fixed_dist is not None: + return fixed_dist, plane_width, plane_height + ref_sizes = {"x": self.platform.width, + "y": self.platform.depth, + "z": self.platform.height, + } + ref_planes = {"x": (0, 0, 1), + "y": (0, 0, 1), + "z": (0, 1, 0) + } + ref_offsets = {"x": 0, + "y": 0, + "z": - self.platform.depth / 2 + } + translate_axis = {"x": 0, + "y": 1, + "z": 2 + } + fallback_ref_planes = {"x": (0, 1, 0), + "y": (1, 0, 0), + "z": (1, 0, 0) + } + fallback_ref_offsets = {"x": - self.platform.height / 2, + "y": - self.platform.width / 2, + "z": - self.platform.width / 2, + } + ref_size = ref_sizes[cutting_axis] + ref_plane = ref_planes[cutting_axis] + ref_offset = ref_offsets[cutting_axis] + inter = self.mouse_to_plane(self.mousepos[0], self.mousepos[1], + plane_normal = ref_plane, + plane_offset = ref_offset, + local_transform = local_transform) + max_size = max((self.platform.width, + self.platform.depth, + self.platform.height)) + dist = None + if inter is not None and numpy.fabs(inter).max() + max_size / 2 < 2 * max_size: + dist = inter[translate_axis[cutting_axis]] + if dist is None or dist < -0.5 * ref_size or dist > 1.5 * ref_size: + ref_plane = fallback_ref_planes[cutting_axis] + ref_offset = fallback_ref_offsets[cutting_axis] + inter = self.mouse_to_plane(self.mousepos[0], self.mousepos[1], + plane_normal = ref_plane, + plane_offset = ref_offset, + local_transform = False) + if inter is not None and numpy.fabs(inter).max() + max_size / 2 < 2 * max_size: + dist = inter[translate_axis[cutting_axis]] + if dist is not None: + dist = min(1.5 * ref_size, max(-0.5 * ref_size, dist)) + return dist, plane_width, plane_height + +def main(): + app = wx.App(redirect = False) + frame = wx.Frame(None, -1, "GL Window", size = (400, 400)) + StlViewPanel(frame) + frame.Show(True) + app.MainLoop() + app.Destroy() + +if __name__ == "__main__": + main() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/utils.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,228 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import os +import sys +import re +import gettext +import datetime +import subprocess +import shlex +import logging + +# Set up Internationalization using gettext +# searching for installed locales on /usr/share; uses relative folder if not +# found (windows) +def install_locale(domain): + if os.path.exists('/usr/share/pronterface/locale'): + gettext.install(domain, '/usr/share/pronterface/locale', unicode = 1) + elif os.path.exists('/usr/local/share/pronterface/locale'): + gettext.install(domain, '/usr/local/share/pronterface/locale', + unicode = 1) + else: + gettext.install(domain, './locale', unicode = 1) + +class LogFormatter(logging.Formatter): + def __init__(self, format_default, format_info): + super(LogFormatter, self).__init__(format_info) + self.format_default = format_default + self.format_info = format_info + + def format(self, record): + if record.levelno == logging.INFO: + self._fmt = self.format_info + else: + self._fmt = self.format_default + return super(LogFormatter, self).format(record) + +def setup_logging(out, filepath = None, reset_handlers = False): + logger = logging.getLogger() + logger.setLevel(logging.INFO) + if reset_handlers: + logger.handlers = [] + formatter = LogFormatter("[%(levelname)s] %(message)s", "%(message)s") + logging_handler = logging.StreamHandler(out) + logging_handler.setFormatter(formatter) + logger.addHandler(logging_handler) + if filepath: + if os.path.isdir(filepath): + filepath = os.path.join(filepath, "printrun.log") + formatter = LogFormatter("%(asctime)s - [%(levelname)s] %(message)s", "%(asctime)s - %(message)s") + logging_handler = logging.FileHandler(filepath) + logging_handler.setFormatter(formatter) + logger.addHandler(logging_handler) + +def iconfile(filename): + if hasattr(sys, "frozen") and sys.frozen == "windows_exe": + return sys.executable + else: + return pixmapfile(filename) + +def imagefile(filename): + for prefix in ['/usr/local/share/pronterface/images', + '/usr/share/pronterface/images']: + candidate = os.path.join(prefix, filename) + if os.path.exists(candidate): + return candidate + local_candidate = os.path.join(os.path.dirname(sys.argv[0]), + "images", filename) + if os.path.exists(local_candidate): + return local_candidate + else: + return os.path.join("images", filename) + +def lookup_file(filename, prefixes): + local_candidate = os.path.join(os.path.dirname(sys.argv[0]), filename) + if os.path.exists(local_candidate): + return local_candidate + for prefix in prefixes: + candidate = os.path.join(prefix, filename) + if os.path.exists(candidate): + return candidate + return filename + +def pixmapfile(filename): + return lookup_file(filename, ['/usr/local/share/pixmaps', + '/usr/share/pixmaps']) + +def sharedfile(filename): + return lookup_file(filename, ['/usr/local/share/pronterface', + '/usr/share/pronterface']) + +def configfile(filename): + return lookup_file(filename, [os.path.expanduser("~/.printrun/"), ]) + +def decode_utf8(s): + try: + s = s.decode("utf-8") + except: + pass + return s + +def format_time(timestamp): + return datetime.datetime.fromtimestamp(timestamp).strftime("%H:%M:%S") + +def format_duration(delta): + return str(datetime.timedelta(seconds = int(delta))) + +def prepare_command(command, replaces = None): + command = shlex.split(command.replace("\\", "\\\\").encode()) + if replaces: + replaces["$python"] = sys.executable + for pattern, rep in replaces.items(): + command = [bit.replace(pattern, rep) for bit in command] + command = [bit.encode() for bit in command] + return command + +def run_command(command, replaces = None, stdout = subprocess.STDOUT, stderr = subprocess.STDOUT, blocking = False): + command = prepare_command(command, replaces) + if blocking: + return subprocess.call(command) + else: + return subprocess.Popen(command, stderr = stderr, stdout = stdout) + +def get_command_output(command, replaces): + p = run_command(command, replaces, + stdout = subprocess.PIPE, stderr = subprocess.STDOUT, + blocking = False) + return p.stdout.read() + +def dosify(name): + return os.path.split(name)[1].split(".")[0][:8] + ".g" + +class RemainingTimeEstimator(object): + + drift = None + gcode = None + + def __init__(self, gcode): + self.drift = 1 + self.previous_layers_estimate = 0 + self.current_layer_estimate = 0 + self.current_layer_lines = 0 + self.gcode = gcode + self.remaining_layers_estimate = sum(layer.duration for layer in gcode.all_layers) + if len(gcode) > 0: + self.update_layer(0, 0) + + def update_layer(self, layer, printtime): + self.previous_layers_estimate += self.current_layer_estimate + if self.previous_layers_estimate > 1. and printtime > 1.: + self.drift = printtime / self.previous_layers_estimate + self.current_layer_estimate = self.gcode.all_layers[layer].duration + self.current_layer_lines = len(self.gcode.all_layers[layer]) + self.remaining_layers_estimate -= self.current_layer_estimate + self.last_idx = -1 + self.last_estimate = None + + def __call__(self, idx, printtime): + if not self.current_layer_lines: + return (0, 0) + if idx == self.last_idx: + return self.last_estimate + layer, line = self.gcode.idxs(idx) + layer_progress = (1 - (float(line + 1) / self.current_layer_lines)) + remaining = layer_progress * self.current_layer_estimate + self.remaining_layers_estimate + estimate = self.drift * remaining + total = estimate + printtime + self.last_idx = idx + self.last_estimate = (estimate, total) + return self.last_estimate + +def parse_build_dimensions(bdim): + # a string containing up to six numbers delimited by almost anything + # first 0-3 numbers specify the build volume, no sign, always positive + # remaining 0-3 numbers specify the coordinates of the "southwest" corner of the build platform + # "XXX,YYY" + # "XXXxYYY+xxx-yyy" + # "XXX,YYY,ZZZ+xxx+yyy-zzz" + # etc + bdl = re.findall("([-+]?[0-9]*\.?[0-9]*)", bdim) + defaults = [200, 200, 100, 0, 0, 0, 0, 0, 0] + bdl = filter(None, bdl) + bdl_float = [float(value) if value else defaults[i] for i, value in enumerate(bdl)] + if len(bdl_float) < len(defaults): + bdl_float += [defaults[i] for i in range(len(bdl_float), len(defaults))] + for i in range(3): # Check for nonpositive dimensions for build volume + if bdl_float[i] <= 0: bdl_float[i] = 1 + return bdl_float + +def get_home_pos(build_dimensions): + return build_dimensions[6:9] if len(build_dimensions) >= 9 else None + +def hexcolor_to_float(color, components): + color = color[1:] + numel = len(color) + ndigits = numel / components + div = 16 ** ndigits - 1 + return tuple(round(float(int(color[i:i + ndigits], 16)) / div, 2) + for i in range(0, numel, ndigits)) + +def check_rgb_color(color): + if len(color[1:]) % 3 != 0: + ex = ValueError(_("Color must be specified as #RGB")) + ex.from_validator = True + raise ex + +def check_rgba_color(color): + if len(color[1:]) % 4 != 0: + ex = ValueError(_("Color must be specified as #RGBA")) + ex.from_validator = True + raise ex + +tempreport_exp = re.compile("([TB]\d*):([-+]?\d*\.?\d*)(?: ?\/)?([-+]?\d*\.?\d*)") +def parse_temperature_report(report): + matches = tempreport_exp.findall(report) + return dict((m[0], (m[1], m[2])) for m in matches) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/zscaper.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/printrun/zscaper.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,80 @@ +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import wx +from stltool import stl, genfacet, emitstl +a = wx.App() + +def genscape(data = [[0, 1, 0, 0], [1, 0, 2, 0], [1, 0, 0, 0], [0, 1, 0, 1]], + pscale = 1.0, bheight = 1.0, zscale = 1.0): + o = stl(None) + datal = len(data) + datah = len(data[0]) + # create bottom: + bmidpoint = (pscale * (datal - 1) / 2.0, pscale * (datah - 1) / 2.0) + # print range(datal), bmidpoint + for i in zip(range(datal + 1)[:-1], range(datal + 1)[1:])[:-1]: + # print (pscale*i[0], pscale*i[1]) + o.facets += [[[0, 0, -1], [[0.0, pscale * i[0], 0.0], [0.0, pscale * i[1], 0.0], [bmidpoint[0], bmidpoint[1], 0.0]]]] + o.facets += [[[0, 0, -1], [[2.0 * bmidpoint[1], pscale * i[1], 0.0], [2.0 * bmidpoint[1], pscale * i[0], 0.0], [bmidpoint[0], bmidpoint[1], 0.0]]]] + o.facets += [genfacet([[0.0, pscale * i[0], data[i[0]][0] * zscale + bheight], [0.0, pscale * i[1], data[i[1]][0] * zscale + bheight], [0.0, pscale * i[1], 0.0]])] + o.facets += [genfacet([[2.0 * bmidpoint[1], pscale * i[1], data[i[1]][datah - 1] * zscale + bheight], [2.0 * bmidpoint[1], pscale * i[0], data[i[0]][datah - 1] * zscale + bheight], [2.0 * bmidpoint[1], pscale * i[1], 0.0]])] + o.facets += [genfacet([[0.0, pscale * i[0], data[i[0]][0] * zscale + bheight], [0.0, pscale * i[1], 0.0], [0.0, pscale * i[0], 0.0]])] + o.facets += [genfacet([[2.0 * bmidpoint[1], pscale * i[1], 0.0], [2.0 * bmidpoint[1], pscale * i[0], data[i[0]][datah - 1] * zscale + bheight], [2.0 * bmidpoint[1], pscale * i[0], 0.0]])] + for i in zip(range(datah + 1)[: - 1], range(datah + 1)[1:])[: - 1]: + # print (pscale * i[0], pscale * i[1]) + o.facets += [[[0, 0, -1], [[pscale * i[1], 0.0, 0.0], [pscale * i[0], 0.0, 0.0], [bmidpoint[0], bmidpoint[1], 0.0]]]] + o.facets += [[[0, 0, -1], [[pscale * i[0], 2.0 * bmidpoint[0], 0.0], [pscale * i[1], 2.0 * bmidpoint[0], 0.0], [bmidpoint[0], bmidpoint[1], 0.0]]]] + o.facets += [genfacet([[pscale * i[1], 0.0, data[0][i[1]] * zscale + bheight], [pscale * i[0], 0.0, data[0][i[0]] * zscale + bheight], [pscale * i[1], 0.0, 0.0]])] + o.facets += [genfacet([[pscale * i[0], 2.0 * bmidpoint[0], data[datal - 1][i[0]] * zscale + bheight], [pscale * i[1], 2.0 * bmidpoint[0], data[datal - 1][i[1]] * zscale + bheight], [pscale * i[1], 2.0 * bmidpoint[0], 0.0]])] + o.facets += [genfacet([[pscale * i[1], 0.0, 0.0], [pscale * i[0], 0.0, data[0][i[0]] * zscale + bheight], [pscale * i[0], 0.0, 0.0]])] + o.facets += [genfacet([[pscale * i[0], 2.0 * bmidpoint[0], data[datal - 1][i[0]] * zscale + bheight], [pscale * i[1], 2.0 * bmidpoint[0], 0.0], [pscale * i[0], 2.0 * bmidpoint[0], 0.0]])] + for i in xrange(datah - 1): + for j in xrange(datal - 1): + o.facets += [genfacet([[pscale * i, pscale * j, data[j][i] * zscale + bheight], [pscale * (i + 1), pscale * (j), data[j][i + 1] * zscale + bheight], [pscale * (i + 1), pscale * (j + 1), data[j + 1][i + 1] * zscale + bheight]])] + o.facets += [genfacet([[pscale * (i), pscale * (j + 1), data[j + 1][i] * zscale + bheight], [pscale * i, pscale * j, data[j][i] * zscale + bheight], [pscale * (i + 1), pscale * (j + 1), data[j + 1][i + 1] * zscale + bheight]])] + # print o.facets[-1] + return o +def zimage(name, out): + i = wx.Image(name) + s = i.GetSize() + print len(map(ord, i.GetData()[::3])) + b = map(ord, i.GetData()[::3]) + data = [] + for i in xrange(s[0]): + data += [b[i * s[1]:(i + 1) * s[1]]] + # data = [i[::5] for i in data[::5]] + emitstl(out, genscape(data, zscale = 0.1).facets, name) + +""" +class scapewin(wx.Frame): + def __init__(self, size = (400, 530)): + wx.Frame.__init__(self, None, + title = "Right-click to load an image", size = size) + self.SetIcon(wx.Icon("plater.png", wx.BITMAP_TYPE_PNG)) + self.SetClientSize(size) + self.panel = wx.Panel(self, size = size) + + +""" +if __name__ == '__main__': + """ + app = wx.App(False) + main = scapewin() + main.Show() + app.MainLoop() +""" + zimage("catposthtmap2.jpg", "testobj.stl") +del a diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronsole.appdata.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/pronsole.appdata.xml Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,15 @@ + + + pronsole.desktop + CC0 + 3D printer host software for console + +

Pronsole is a command line user interface for desktop 3D printers, such as RepRap. It lets you to load Gcode, connect to printer and send the Gcode to it. Best option for controling 3D printer from headless or not enough powerful machines.

+

It allows you not only to send Gcode form file, but also control the printer manually or send Gcode commands directly to the printer.

+

Is integrates slicing tool, so if you load an STL 3D model to it as well as Gcode.

+
+ + https://raw.github.com/kliment/Printrun/master/screenshots/pronsole.png + + https://github.com/kliment/Printrun +
diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronsole.desktop --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/pronsole.desktop Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=Pronsole +GenericName=Printer console +Comment=Controls your 3D printer form console +Icon=/usr/share/pixmaps/pronsole.png +Exec=/usr/bin/pronsole.py +StartupNotify=true +Terminal=true +Categories=Utility;Graphics;3DGraphics;ConsoleOnly; diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronsole.ico Binary file printrun-src/pronsole.ico has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronsole.png Binary file printrun-src/pronsole.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronsole.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/pronsole.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import sys +import traceback +import logging +from printrun.pronsole import pronsole +import getopt + +if __name__ == "__main__": + + from printrun.printcore import __version__ as printcore_version + + usage = "Usage:\n"+\ + " pronsole [OPTION]\n\n"+\ + "Options:\n"+\ + " -V, --version\t\t\tPrint program's version number and exit\n"+\ + " -h, --help\t\t\tPrint this help message and exit\n"+\ + " -c, --conf\t\t\tLoad this file on startup instead of .pronsolerc; you may chain config files, if so settings auto-save will use the last specified file\n"+\ + " -e, --execute\t\t\tExecutes command after configuration/.pronsolerc is loaded; macros/settings from these commands are not autosaved" + + try: + opts, args = getopt.getopt(sys.argv[1:], "hVce", ["help", "version", "conf", "execute"]) + except getopt.GetoptError, err: + print str(err) + print usage + sys.exit(2) + for o, a in opts: + if o in ('-V','--version'): + print "printrun "+printcore_version + sys.exit(0) + elif o in ('-h', '--help'): + print usage + sys.exit(0) + + interp = pronsole() + interp.parse_cmdline(sys.argv[1:]) + try: + interp.cmdloop() + except SystemExit: + interp.p.disconnect() + except: + logging.error(_("Caught an exception, exiting:") + + "\n" + traceback.format_exc()) + interp.p.disconnect() diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronterface.appdata.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/pronterface.appdata.xml Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,16 @@ + + + pronterface.desktop + CC0 + 3D printer host software + +

Pronterface is a graphical user interface for desktop 3D printers, such as RepRap. It lets you view Gcode, connect to printer and send the Gcode to it. It's feature rich yet minimalist application.

+

It allows you not only to send Gcode form file, but also control the printer manually or send Gcode commands directly trough input field.

+

Is integrates slicing tool, so if you load an STL 3D model to it, it will slice and load it automatically.

+
+ + https://raw.github.com/kliment/Printrun/master/screenshots/pronterface.png + https://raw.github.com/kliment/Printrun/master/screenshots/pronterface2.png + + https://github.com/kliment/Printrun +
diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronterface.desktop --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/pronterface.desktop Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=Pronterface +GenericName=Printer Interface +Comment=Controls your 3D printer +Icon=/usr/share/pixmaps/pronterface.png +Exec=/usr/bin/pronterface.py %f +Path=/usr/share/pronterface/ +StartupNotify=true +Terminal=false +Categories=GNOME;GTK;Utility;Graphics;3DGraphics; +MimeType=text/plain;application/octet-stream;application/sla; diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronterface.ico Binary file printrun-src/pronterface.ico has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronterface.png Binary file printrun-src/pronterface.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/pronterface.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/pronterface.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import sys +import getopt + +try: + import wx # NOQA +except: + print("wxPython is not installed. This program requires wxPython to run.") + if sys.version_info.major >= 3: + print("""\ +As you are currently running python3, this is most likely because wxPython is +not yet available for python3. You should try running with python2 instead.""") + sys.exit(-1) + else: + raise + +from printrun.pronterface import PronterApp + +if __name__ == '__main__': + + from printrun.printcore import __version__ as printcore_version + + usage = "Usage:\n"+\ + " pronterface [OPTION]\n\n"+\ + "Options:\n"+\ + " -V, --version\t\t\tPrint program's version number and exit\n"+\ + " -h, --help\t\t\tPrint this help message and exit\n"+\ + " -a, --autoconnect\t\tAutomatically try to connect to printer on startup\n"+\ + " -c, --conf\t\t\tLoad this file on startup instead of .pronsolerc; you may chain config files, if so settings auto-save will use the last specified file\n"+\ + " -e, --execute\t\t\tExecutes command after configuration/.pronsolerc is loaded; macros/settings from these commands are not autosaved" + + try: + opts, args = getopt.getopt(sys.argv[1:], "hVcea", ["help", "version", "conf", "execute", "autoconnect"]) + except getopt.GetoptError, err: + print str(err) + print usage + sys.exit(2) + for o, a in opts: + if o in ('-V','--version'): + print "printrun "+printcore_version + sys.exit(0) + elif o in ('-h', '--help'): + print usage + sys.exit(0) + + app = PronterApp(False) + try: + app.MainLoop() + except KeyboardInterrupt: + pass + del app diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/release_windows.bat --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/release_windows.bat Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,6 @@ +d:\python27\python.exe setup_win.py py2exe -v +xcopy images dist\images\ /Y /E +xcopy locale dist\locale\ /Y /E +xcopy Slic3r dist\Slic3r\ /Y /E +copy MSVCP90.DLL dist\ +pause diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/requirements.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/requirements.txt Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,9 @@ +argparse +pyreadline +pyserial +wxPython +numpy +pyglet>=1.1 +pycairo +cairosvg +psutil>=2.0 diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/screenshots/plater.png Binary file printrun-src/screenshots/plater.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/screenshots/pronsole.png Binary file printrun-src/screenshots/pronsole.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/screenshots/pronterface.png Binary file printrun-src/screenshots/pronterface.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/screenshots/pronterface2.png Binary file printrun-src/screenshots/pronterface2.png has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/setup.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,172 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import sys +import os +from stat import S_IRUSR, S_IWUSR, S_IRGRP, S_IROTH +from distutils.core import setup +from distutils.command.install import install as _install +from distutils.command.install_data import install_data as _install_data +try: + from Cython.Build import cythonize + extensions = cythonize("printrun/gcoder_line.pyx") + from Cython.Distutils import build_ext +except ImportError, e: + print "WARNING: Failed to cythonize: %s" % e + # Debug helper: uncomment these: + # import traceback + # traceback.print_exc() + extensions = None + build_ext = None + +from printrun.printcore import __version__ as printcore_version + +INSTALLED_FILES = "installed_files" + +class install (_install): + + def run(self): + _install.run(self) + outputs = self.get_outputs() + length = 0 + if self.root: + length += len(self.root) + if self.prefix: + length += len(self.prefix) + if length: + for counter in xrange(len(outputs)): + outputs[counter] = outputs[counter][length:] + data = "\n".join(outputs) + try: + file = open(INSTALLED_FILES, "w") + except: + self.warn("Could not write installed files list %s" % + INSTALLED_FILES) + return + file.write(data) + file.close() + +class install_data(_install_data): + + def run(self): + def chmod_data_file(file): + try: + os.chmod(file, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) + except: + self.warn("Could not chmod data file %s" % file) + _install_data.run(self) + map(chmod_data_file, self.get_outputs()) + +class uninstall(_install): + + def run(self): + try: + file = open(INSTALLED_FILES, "r") + except: + self.warn("Could not read installed files list %s" % + INSTALLED_FILES) + return + files = file.readlines() + file.close() + prepend = "" + if self.root: + prepend += self.root + if self.prefix: + prepend += self.prefix + if len(prepend): + for counter in xrange(len(files)): + files[counter] = prepend + files[counter].rstrip() + for file in files: + print "Uninstalling", file + try: + os.unlink(file) + except: + self.warn("Could not remove file %s" % file) + +ops = ("install", "build", "sdist", "uninstall", "clean", "build_ext") + +if len(sys.argv) < 2 or sys.argv[1] not in ops: + print "Please specify operation : %s" % " | ".join(ops) + raise SystemExit + +prefix = None +if len(sys.argv) > 2: + i = 0 + for o in sys.argv: + if o.startswith("--prefix"): + if o == "--prefix": + if len(sys.argv) >= i: + prefix = sys.argv[i + 1] + sys.argv.remove(prefix) + elif o.startswith("--prefix=") and len(o[9:]): + prefix = o[9:] + sys.argv.remove(o) + i += 1 +if not prefix and "PREFIX" in os.environ: + prefix = os.environ["PREFIX"] +if not prefix or not len(prefix): + prefix = sys.prefix + +if sys.argv[1] in("install", "uninstall") and len(prefix): + sys.argv += ["--prefix", prefix] + +target_images_path = "share/pronterface/images/" +data_files = [('share/pixmaps', ['pronterface.png', 'plater.png', 'pronsole.png']), + ('share/applications', ['pronterface.desktop', 'pronsole.desktop', 'plater.desktop']), + ('share/appdata', ['pronterface.appdata.xml', 'pronsole.appdata.xml', 'plater.appdata.xml'])] + +for basedir, subdirs, files in os.walk("images"): + images = [] + for filename in files: + if filename.find(".svg") or filename.find(".png"): + file_path = os.path.join(basedir, filename) + images.append(file_path) + data_files.append((target_images_path + basedir[len("images/"):], images)) + +for basedir, subdirs, files in os.walk("locale"): + if not basedir.endswith("LC_MESSAGES"): + continue + destpath = os.path.join("share", "pronterface", basedir) + files = filter(lambda x: x.endswith(".mo"), files) + files = map(lambda x: os.path.join(basedir, x), files) + data_files.append((destpath, files)) + +extra_data_dirs = ["css"] +for extra_data_dir in extra_data_dirs: + for basedir, subdirs, files in os.walk(extra_data_dir): + files = map(lambda x: os.path.join(basedir, x), files) + destpath = os.path.join("share", "pronterface", basedir) + data_files.append((destpath, files)) + +cmdclass = {"uninstall": uninstall, + "install": install, + "install_data": install_data} +if build_ext: + cmdclass['build_ext'] = build_ext + +setup(name = "Printrun", + version = printcore_version, + description = "Host software for 3D printers", + author = "Kliment Yanev", + url = "http://github.com/kliment/Printrun/", + license = "GPLv3", + data_files = data_files, + packages = ["printrun", "printrun.gl", "printrun.gl.libtatlin", "printrun.gui", "printrun.power"], + scripts = ["pronsole.py", "pronterface.py", "plater.py", "printcore.py"], + cmdclass = cmdclass, + ext_modules = extensions, + ) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/setup_win.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/setup_win.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,14 @@ +from distutils.core import setup + +setup(windows = [{"script": "pronterface.py", "icon_resources": [(1, "P-face.ico")]}, + {"script": "plater.py", "icon_resources": [(1, "plater.ico")]}, + ], + console = [{"script": "pronsole.py", "icon_resources": [(1, "pronsole.ico")]}, + ], + options = {"py2exe": {"bundle_files": 1, + "dll_excludes": ["w9xpopen.exe"], + "compressed": 1, + "excludes": ['_ssl', 'pickle', 'calendar', 'Tkconstants', 'Tkinter', 'tcl', 'email'] + } + } + ) diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/testfiles/PCB-milling-and-(comment).gcode --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/testfiles/PCB-milling-and-(comment).gcode Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,64 @@ +(Created by G-code exporter) +(Fri Apr 27 22:20:09 2012) +(Board size: 100.00 x 130.00 mm) +(---------------------------------) +G21 +G90 +G0 X14.392 Y30.94113 Z1. +G4 +M104 S255 +G1 Z-0.2 F60 +G1 X14.05334 Y30.60247 +G1 X12.02134 Y30.60247 +G1 X11.598 Y30.85647 +G1 X11.00534 Y31.5338 +G1 X10.074 Y32.5498 +G1 X9.98933 Y34.41247 +G0 Z1. +(RepRap Gen7 v1.4) +G0 Z1. +(R) +G0 X9.9753 Y6.9723 +G1 Z-0.20 F60 +G1 X11.4761 Y6.9723 F250 +G1 X11.8514 Y6.5971 +G1 X11.8514 Y5.8467 +G1 X11.4761 Y5.4715 +G1 X10.3505 Y5.4715 +G1 X11.8514 Y3.9456 +G0 Z1. +G0 X10.3505 Y6.9723 +G1 Z-0.20 F60 +G1 X10.3505 Y3.9456 F250 +G0 Z1. +(e) +G0 X12.7519 Y4.696 +G1 Z-0.20 F60 +G1 X14.2778 Y4.696 F250 +G1 X14.2778 Y5.0962 +G1 X13.9025 Y5.4715 +G1 X13.1271 Y5.4715 +G1 X12.7519 Y5.0962 +G1 X12.7519 Y4.3208 +G1 X13.1271 Y3.9456 +G1 X14.2778 Y3.9456 +G0 Z1. +(p) +G0 X15.5535 Y2.8199 +G1 Z-0.20 F60 +G1 X15.5535 Y5.0962 F250 +G1 X15.1783 Y5.4715 +G1 X15.5535 Y5.0962 +G1 X15.9287 Y5.4715 +G1 X16.6792 Y5.4715 +G1 X17.0544 Y5.0962 +G1 X17.0544 Y4.3208 +G1 X16.6792 Y3.9456 +G1 X15.9287 Y3.9456 +G1 X15.5535 Y4.3208 +G0 Z1. +G4 +M104 S0 +(tool change position) +G1 X2. Y2. Z40. F400 +M2 diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/testfiles/belt_pulley3.skeinforge.large.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/testfiles/belt_pulley3.skeinforge.large.svg Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,655 @@ + + + + + + + + belt_pulley3.stl - Slice Layers + + + + + + Layer 0, z:-1.8 + + + + + Layer 1, z:-1.4 + + + + + Layer 2, z:-1.0 + + + + + Layer 3, z:-0.6 + + + + + Layer 4, z:-0.2 + + + + + Layer 5, z:0.2 + + + + + Layer 6, z:0.6 + + + + + Layer 7, z:1.0 + + + + + Layer 8, z:1.4 + + + + + Layer 9, z:1.8 + + + + + Layer 10, z:2.2 + + + + + Layer 11, z:2.6 + + + + + Layer 12, z:3.0 + + + + + Layer 13, z:3.4 + + + + + Layer 14, z:3.8 + + + + + Layer 15, z:4.2 + + + + + Layer 16, z:4.6 + + + + + Layer 17, z:5.0 + + + + + Layer 18, z:5.4 + + + + + Layer 19, z:5.8 + + + + + Layer 20, z:6.2 + + + + + Layer 21, z:6.6 + + + + + Layer 22, z:7.0 + + + + + Layer 23, z:7.4 + + + + + Layer 24, z:7.8 + + + + + Layer 25, z:8.2 + + + + + Layer 26, z:8.6 + + + + + Layer 27, z:9.0 + + + + + Layer 28, z:9.4 + + + + + Layer 29, z:9.8 + + + + + + + + + + + + + Latitude + < + > + Longitude + < + > + Scale + 1 + < + > + + Min + X: -11.919 mm + Y: -11.919 mm + Z: -2.0 mm + + + Max + X: 11.919 mm + Y: 11.919 mm + Z: 10.0 mm + + + Dimension + X: 23.838 mm + Y: 23.838 mm + Z: 12.0 mm + + + Statistics + Layer Height: 0.4 mm + Number of Layers: 30 + Volume: 1.8836 cm3 + + + + + + + + Y + X + 0 + + + 1 + Layer + < + > + Scale + 1 + < + > + + Min + X: -11.919 mm + Y: -11.919 mm + Z: -2.0 mm + + + Max + X: 11.919 mm + Y: 11.919 mm + Z: 10.0 mm + + + Dimension + X: 23.838 mm + Y: 23.838 mm + Z: 12.0 mm + + + Statistics + Layer Height: 0.4 mm + Number of Layers: 30 + Volume: 1.8836 cm3 + + + + + + + Y + X + Scale + : 1 + < + > + + Min + X: -11.919 mm + Y: -11.919 mm + Z: -2.0 mm + + + Max + X: 11.919 mm + Y: 11.919 mm + Z: 10.0 mm + + + Dimension + X: 23.838 mm + Y: 23.838 mm + Z: 12.0 mm + + + Statistics + Layer Height: 0.4 mm + Number of Layers: 30 + Volume: 1.8836 cm3 + + + [Iso View] + Iso View + [Layer View] + Layer View + [Scroll View] + Scroll View + + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/testfiles/belt_pulley3.skeinforge.small.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/testfiles/belt_pulley3.skeinforge.small.svg Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,515 @@ + + + + + + + + belt_pulley3.stl - Slice Layers + + + + + + Layer 0, z:-1.8 + + + + + Layer 1, z:-1.4 + + + + + + + + + + + + + Latitude + < + > + Longitude + < + > + Scale + 1 + < + > + + Min + X: -11.919 mm + Y: -11.919 mm + Z: -2.0 mm + + + Max + X: 11.919 mm + Y: 11.919 mm + Z: 10.0 mm + + + Dimension + X: 23.838 mm + Y: 23.838 mm + Z: 12.0 mm + + + Statistics + Layer Height: 0.4 mm + Number of Layers: 30 + Volume: 1.8836 cm3 + + + + + + + + Y + X + 0 + + + 1 + Layer + < + > + Scale + 1 + < + > + + Min + X: -11.919 mm + Y: -11.919 mm + Z: -2.0 mm + + + Max + X: 11.919 mm + Y: 11.919 mm + Z: 10.0 mm + + + Dimension + X: 23.838 mm + Y: 23.838 mm + Z: 12.0 mm + + + Statistics + Layer Height: 0.4 mm + Number of Layers: 30 + Volume: 1.8836 cm3 + + + + + + + Y + X + Scale + : 1 + < + > + + Min + X: -11.919 mm + Y: -11.919 mm + Z: -2.0 mm + + + Max + X: 11.919 mm + Y: 11.919 mm + Z: 10.0 mm + + + Dimension + X: 23.838 mm + Y: 23.838 mm + Z: 12.0 mm + + + Statistics + Layer Height: 0.4 mm + Number of Layers: 30 + Volume: 1.8836 cm3 + + + [Iso View] + Iso View + [Layer View] + Layer View + [Scroll View] + Scroll View + + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/testfiles/belt_pulley3.slic3r.large.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/testfiles/belt_pulley3.slic3r.large.svg Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,488 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/testfiles/belt_pulley3.slic3r.small.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/testfiles/belt_pulley3.slic3r.small.svg Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/testfiles/grid.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/testfiles/grid.svg Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/testfiles/out.3dlp.zip Binary file printrun-src/testfiles/out.3dlp.zip has changed diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/testfiles/quick-test.gcode --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/testfiles/quick-test.gcode Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,101 @@ +; THIS IS A TZST. DO NOT ATTZMPT TO PRINT THIS FILZ. +; gZnZratZd by Slic3r 0.9.3-dZv on 2012-09-02 at 04:02:31 + +; layZr_hZight = 0.4 +; pZrimZtZrs = 3 +; solid_layZrs = 3 +; fill_dZnsity = 0.4 +; pZrimZtZr_spZZd = 30 +; infill_spZZd = 60 +; travZl_spZZd = 130 +; scalZ = 1 +; nozzlZ_diamZtZr = 0.5 +; filamZnt_diamZtZr = 3 +; Zxtrusion_multipliZr = 1 +; singlZ wall width = 0.53mm +; first layZr singlZ wall width = 0.80mm + +M104 S200 ; sZt tZmpZraturZ +;G28 ; homZ all axZs +;M109 S200 ; wait for tZmpZraturZ to bZ rZachZd +G90 ; usZ absolutZ coordinatZs +G21 ; sZt units to millimZtZrs +G92 Z0 +M82 ; usZ absolutZ distancZs for Zxtrusion +G1 Z0.400 F71800.000 +G1 X75.725 Y86.681 +G1 F1800.000 Z1.00000 +G1 X87.905 Y75.241 F1040.000 Z1.69560 +G1 X88.365 Y74.871 Z1.72017 +G1 X88.865 Y74.541 Z1.74511 +G1 X89.395 Y74.261 Z1.77006 +G1 X89.945 Y74.031 Z1.79488 +G1 X90.225 Y73.931 Z1.80726 +G1 X90.805 Y73.771 Z1.83230 +G1 X92.375 Y73.501 Z1.89862 +G1 X92.935 Y73.471 Z1.92196 +G1 X109.165 Y73.961 Z2.59789 +G1 X109.475 Y73.991 Z2.61085 +G1 X110.105 Y74.101 Z2.63747 +G1 X110.715 Y74.271 Z2.66383 +G1 X111.795 Y74.681 Z2.71192 +G1 X112.355 Y74.951 Z2.73780 +G1 X112.875 Y75.271 Z2.76322 +G1 X113.135 Y75.451 Z2.77638 +G1 X113.615 Y75.841 Z2.80213 +G1 X124.855 Y87.841 Z3.48656 +G1 X125.485 Y88.631 Z3.52863 +G1 X125.975 Y89.351 Z3.56488 +G1 X126.385 Y90.111 Z3.60083 +G1 X126.605 Y90.651 Z3.62510 +G1 X126.775 Y91.201 Z3.64906 +G1 X126.905 Y91.771 Z3.67340 +G1 X126.975 Y92.341 Z3.69731 +G1 X127.005 Y92.921 Z3.72148 +G1 X126.325 Y109.851 Z4.42681 +G1 X126.255 Y110.391 Z4.44947 +G1 X126.145 Y110.921 Z4.47201 +G1 X125.995 Y111.441 Z4.49453 +G1 X125.805 Y111.951 Z4.51719 +G1 X125.575 Y112.441 Z4.53972 +G1 X125.165 Y113.131 Z4.57313 +G1 X124.835 Y113.571 Z4.59603 +G1 X124.485 Y113.971 Z4.61815 +G1 X124.095 Y114.351 Z4.64082 +G1 X123.885 Y114.531 Z4.65233 +G1 X123.005 Y115.151 Z4.69715 +G1 X122.525 Y115.401 Z4.71967 +G1 X96.195 Y125.661 Z5.89600 +G1 X95.515 Y125.881 Z5.92575 +G1 X94.385 Y126.141 Z5.97402 +G1 X91.335 Y126.551 Z6.10213 +G1 X91.055 Y126.561 Z6.11379 +G1 X90.775 Y126.561 Z6.12545 +G1 X90.215 Y126.521 Z6.14882 +G1 X89.375 Y126.371 Z6.18434 +G1 X88.835 Y126.221 Z6.20767 +G1 X88.305 Y126.021 Z6.23125 +G1 X87.795 Y125.781 Z6.25471 +G1 X87.075 Y125.341 Z6.28984 +G1 X86.415 Y124.811 Z6.32507 +G1 X75.155 Y112.801 Z7.01038 +G1 X74.955 Y112.561 Z7.02339 +G1 X74.595 Y112.041 Z7.04972 +G1 X73.825 Y110.701 Z7.11405 +G1 X73.695 Y110.441 Z7.12615 +G1 X73.475 Y109.911 Z7.15004 +G1 X73.305 Y109.361 Z7.17400 +G1 X73.175 Y108.801 Z7.19794 +G1 X73.095 Y108.231 Z7.22190 +G1 X73.065 Y107.651 Z7.24607 +G1 X73.615 Y91.111 Z7.93497 +G1 X73.725 Y90.281 Z7.96982 +G1 X73.785 Y90.011 Z7.98134 +G1 X74.035 Y89.211 Z8.01623 +G1 X74.255 Y88.701 Z8.03935 +G1 X74.515 Y88.201 Z8.06281 +G1 X74.815 Y87.731 Z8.08602 +G1 X75.335 Y87.081 Z8.12067 +G1 X75.640 Y86.766 Z8.13893 + + diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/testtools/gcodeviewer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printrun-src/testtools/gcodeviewer.py Fri Jun 03 09:16:07 2016 +0200 @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +# This file is part of the Printrun suite. +# +# Printrun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Printrun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Printrun. If not, see . + +import sys +import os +import logging +logging.basicConfig(level=logging.INFO) + +import wx + +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) + +from printrun.gcview import GcodeViewFrame +from printrun import gcoder + +app = wx.App(redirect = False) +build_dimensions = [200, 200, 100, -100, -100, 0] +build_dimensions = [200, 200, 100, 0, 0, 0] +frame = GcodeViewFrame(None, wx.ID_ANY, 'Gcode view, shift to move view, mousewheel to set layer', size = (800, 800), build_dimensions = build_dimensions) +gcode = gcoder.GCode(open(sys.argv[1])) +print "Gcode loaded" +frame.addfile(gcode) + +first_move = None +for i in range(len(gcode.lines)): + if gcode.lines[i].is_move: + first_move = gcode.lines[i] + break +last_move = None +for i in range(len(gcode.lines) - 1, -1, -1): + if gcode.lines[i].is_move: + last_move = gcode.lines[i] + break +nsteps = 20 +steptime = 50 +lines = [first_move] \ + + [gcode.lines[int(float(i) * (len(gcode.lines) - 1) / nsteps)] + for i in range(1, nsteps)] + [last_move] +current_line = 0 +def setLine(): + global current_line + frame.set_current_gline(lines[current_line]) + current_line = (current_line + 1) % len(lines) + timer.Start() + +timer = wx.CallLater(steptime, setLine) +timer.Start() + +frame.Show(True) +app.MainLoop() +app.Destroy()