From f843e2d09a83f57674708aae9cbf352366fe442c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 10 Feb 2016 13:25:58 +0100 Subject: [PATCH 001/297] Initial commit --- .gitignore | 62 +++++ LICENSE | 661 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 3 files changed, 725 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..1dbc687de0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,62 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..dbbe355815 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..e0573ec934 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# JediWifiPrintingPlugin +Secret plugin to enable wifi printing from Cura to JediPrinter From 763206af3247999015f321301e9074e83c21b136 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 10 Feb 2016 13:29:17 +0100 Subject: [PATCH 002/297] Initial commit --- HttpUploadDataStream.py | 32 +++++++++ WifiConnection.py | 142 ++++++++++++++++++++++++++++++++++++++ WifiOutputDevicePlugin.py | 53 ++++++++++++++ __init__.py | 20 ++++++ 4 files changed, 247 insertions(+) create mode 100644 HttpUploadDataStream.py create mode 100644 WifiConnection.py create mode 100644 WifiOutputDevicePlugin.py create mode 100644 __init__.py diff --git a/HttpUploadDataStream.py b/HttpUploadDataStream.py new file mode 100644 index 0000000000..a3e5a82ba4 --- /dev/null +++ b/HttpUploadDataStream.py @@ -0,0 +1,32 @@ +from UM.Signal import Signal, SignalEmitter +class HttpUploadDataStream(SignalEmitter): + def __init__(self): + super().__init__() + self._data_list = [] + self._total_length = 0 + self._read_position = 0 + + progressSignal = Signal() + + def write(self, data): + data = bytes(data,'UTF-8') + size = len(data) + if size < 1: + return + blocks = int(size / 2048) + for n in range(0, blocks): + self._data_list.append(data[n*2048:n*2048+2048]) + self._data_list.append(data[blocks*2048:]) + self._total_length += size + + def read(self, size): + if self._read_position >= len(self._data_list): + return None + ret = self._data_list[self._read_position] + self._read_position += 1 + + self.progressSignal.emit(float(self._read_position) / float(len(self._data_list))) + return ret + + def __len__(self): + return self._total_length \ No newline at end of file diff --git a/WifiConnection.py b/WifiConnection.py new file mode 100644 index 0000000000..5d1a4a6c9c --- /dev/null +++ b/WifiConnection.py @@ -0,0 +1,142 @@ +from UM.OutputDevice.OutputDevice import OutputDevice +from UM.OutputDevice import OutputDeviceError +import threading +import http.client as httpclient +import urllib +import json +import time +import base64 + +from . import HttpUploadDataStream +from UM.i18n import i18nCatalog +from UM.Signal import Signal, SignalEmitter +from UM.Application import Application +from UM.Logger import Logger +i18n_catalog = i18nCatalog("cura") + +class WifiConnection(OutputDevice, SignalEmitter): + def __init__(self, address, info): + super().__init__(address) + self._address = address + self._info = info + self._http_lock = threading.Lock() + self._http_connection = None + self._file = None + self._do_update = True + self._thread = None + self._state = None + self._is_connected = False + self.connect() + self.setName(address) + self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print with WIFI")) + self.setDescription(i18n_catalog.i18nc("@info:tooltip", "Print with WIFI")) + self.setIconName("print") + + connectionStateChanged = Signal() + + def isConnected(self): + return self._is_connected + + def setConnectionState(self, state): + print("setting connection state: " , self._address, " " , state) + if state != self._is_connected: + self._is_connected = state + self.connectionStateChanged.emit(self._address) + else: + self._is_connected = state + + def _update(self): + while self._thread: + state_reply = self._httpRequest('GET', '/api/v1/printer/state') + if state_reply is not None: + self._state = state_reply + if not self._is_connected: + self.setConnectionState(True) + else: + self._state = {'state': 'CONNECTION_ERROR'} + self.setConnectionState(False) + time.sleep(1) + + def close(self): + self._do_update = False + self._is_connected = False + + def requestWrite(self, node): + self._file = getattr( Application.getInstance().getController().getScene(), "gcode_list") + self.startPrint() + + #Open the active connection to the printer so we can send commands + def connect(self): + if self._thread is None: + self._do_update = True + self._thread = threading.Thread(target = self._update) + self._thread.daemon = True + self._thread.start() + + def getCameraImage(self): + pass #Do Nothing + + def startPrint(self): + try: + result = self._httpRequest('POST', '/api/v1/printer/print_upload', {'print_name': 'Print from Cura', 'parameters':''}, {'file': ('file.gcode', self._file)}) + print(result.get('success',False)) + #result = self._httpRequest('POST', '/api/v1/printer/print_upload', {'print_name': 'Print from Cura'}) + except Exception as e: + Logger.log('e' , 'An exception occured in wifi connection: ' , e) + + def _httpRequest(self, method, path, post_data = None, files = None): + with self._http_lock: + self._http_connection = httpclient.HTTPConnection(self._address, timeout = 30) + try: + if files is not None: + boundary = 'wL36Yn8afVp8Ag7AmP8qZ0SA4n1v9T' + s = HttpUploadDataStream.HttpUploadDataStream() + for k, v in files.items(): + filename = v[0] + file_contents = v[1] + s.write('--%s\r\n' % (boundary)) + s.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (k, filename)) + s.write('Content-Type: application/octet-stream\r\n') + s.write('Content-Transfer-Encoding: binary\r\n') + s.write('\r\n') + + if file_contents is not None: + for line in file_contents: + s.write(str(line)) + + s.write('\r\n') + + for k, v in post_data.items(): + s.write('--%s\r\n' % (boundary)) + s.write('Content-Disposition: form-data; name="%s"\r\n' % (k)) + s.write('\r\n') + s.write(str(v)) + s.write('\r\n') + s.write('--%s--\r\n' % (boundary)) + + self._http_connection.request(method, path, s, {"Content-type": "multipart/form-data; boundary=%s" % (boundary), "Content-Length": len(s)}) + elif post_data is not None: + + self._http_connection.request(method, path, urllib.urlencode(post_data), {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"}) + else: + self._http_connection.request(method, path, headers={"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"}) + except IOError: + self._http_connection.close() + return None + except Exception as e: + self._http_connection.close() + return None + + try: + response = self._http_connection.getresponse() + response_text = response.read() + except IOError: + self._http_connection.close() + return None + try: + response = json.loads(response_text.decode("utf-8")) + except ValueError: + self._http_connection.close() + return None + self._http_connection.close() + return response diff --git a/WifiOutputDevicePlugin.py b/WifiOutputDevicePlugin.py new file mode 100644 index 0000000000..e01c078cde --- /dev/null +++ b/WifiOutputDevicePlugin.py @@ -0,0 +1,53 @@ +from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin +from . import WifiConnection + +from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange +from UM.Signal import Signal, SignalEmitter + +class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): + def __init__(self): + super().__init__() + self._zero_conf = Zeroconf() + self._browser = None + self._connections = {} + self.addConnectionSignal.connect(self.addConnection) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. + + addConnectionSignal = Signal() + + def start(self): + self._browser = ServiceBrowser(Zeroconf(), u'_ultimaker._tcp.local.', [self._onServiceChanged]) + + def stop(self): + self._zero_conf.close() + + ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. + def addConnection(self, name, address, properties): + connection = WifiConnection.WifiConnection(address, properties) + connection.connect() + self._connections[address] = connection + if address == "10.180.1.23": #DEBUG + #if address == "10.180.0.249": #DEBUG + connection.startPrint() + connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + + def _onPrinterConnectionStateChanged(self, address): + print(self._connections[address].isConnected()) + if self._connections[address].isConnected(): + self.getOutputDeviceManager().addOutputDevice(self._connections[address]) + else: + self.getOutputDeviceManager().removeOutputDevice(self._connections[address]) + + def removeConnection(self): + pass + + def _onServiceChanged(self, zeroconf, service_type, name, state_change): + if state_change == ServiceStateChange.Added: + info = zeroconf.get_service_info(service_type, name) + if info: + if info.properties.get(b"type", None): + address = '.'.join(map(lambda n: str(n), info.address)) + self.addConnectionSignal.emit(str(name), address, info.properties) + + elif state_change == ServiceStateChange.Removed: + print("Device disconnected") + #print("HERP DERP") diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000..f06ca20d57 --- /dev/null +++ b/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. +from . import WifiOutputDevicePlugin + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + +def getMetaData(): + return { + "plugin": { + "name": "Wifi connection", + "author": "Ultimaker", + "description": catalog.i18nc("Wifi connection", "Wifi connection"), + "api": 2 + } + } + +def register(app): + return { "output_device": WifiOutputDevicePlugin.WifiOutputDevicePlugin() } + From ca502705c24a34a7659ebf1d0ae2520c077d4235 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 11 Feb 2016 11:14:18 +0100 Subject: [PATCH 003/297] Reworked wifi connection to use requests instead of httpclient This library is far easier to use and much cleaner. CURA-49 --- WifiConnection.py | 101 ++++++++++++-------------------------- WifiOutputDevicePlugin.py | 19 +++---- 2 files changed, 42 insertions(+), 78 deletions(-) diff --git a/WifiConnection.py b/WifiConnection.py index 5d1a4a6c9c..535971e840 100644 --- a/WifiConnection.py +++ b/WifiConnection.py @@ -1,11 +1,10 @@ from UM.OutputDevice.OutputDevice import OutputDevice from UM.OutputDevice import OutputDeviceError import threading -import http.client as httpclient -import urllib import json import time import base64 +import requests from . import HttpUploadDataStream from UM.i18n import i18nCatalog @@ -24,8 +23,13 @@ class WifiConnection(OutputDevice, SignalEmitter): self._file = None self._do_update = True self._thread = None - self._state = None + + self._json_printer_state = None + self._is_connected = False + + self._api_version = "1" + self._api_prefix = "/api/v" + self._api_version + "/" self.connect() self.setName(address) self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print with WIFI")) @@ -37,23 +41,25 @@ class WifiConnection(OutputDevice, SignalEmitter): def isConnected(self): return self._is_connected + ## Set the connection state of this connection. + # Although we use a restfull API, we do poll the api to check if the machine is still responding. def setConnectionState(self, state): - print("setting connection state: " , self._address, " " , state) if state != self._is_connected: + Logger.log("i", "setting connection state of %s to %s " %(self._address, state)) self._is_connected = state self.connectionStateChanged.emit(self._address) else: - self._is_connected = state + self._is_connected = state def _update(self): while self._thread: - state_reply = self._httpRequest('GET', '/api/v1/printer/state') - if state_reply is not None: - self._state = state_reply + self.setConnectionState(True) + reply = self._httpGet("printer") + if reply.status_code == 200: + self._json_printer_state = reply.json() if not self._is_connected: self.setConnectionState(True) else: - self._state = {'state': 'CONNECTION_ERROR'} self.setConnectionState(False) time.sleep(1) @@ -61,11 +67,11 @@ class WifiConnection(OutputDevice, SignalEmitter): self._do_update = False self._is_connected = False - def requestWrite(self, node): + def requestWrite(self, node, file_name = None): self._file = getattr( Application.getInstance().getController().getScene(), "gcode_list") self.startPrint() - #Open the active connection to the printer so we can send commands + ## Start the polling thread. def connect(self): if self._thread is None: self._do_update = True @@ -78,65 +84,22 @@ class WifiConnection(OutputDevice, SignalEmitter): def startPrint(self): try: - result = self._httpRequest('POST', '/api/v1/printer/print_upload', {'print_name': 'Print from Cura', 'parameters':''}, {'file': ('file.gcode', self._file)}) - print(result.get('success',False)) - #result = self._httpRequest('POST', '/api/v1/printer/print_upload', {'print_name': 'Print from Cura'}) + result = self._httpPost("print_job", self._file) except Exception as e: - Logger.log('e' , 'An exception occured in wifi connection: ' , e) + Logger.log("e" , "An exception occured in wifi connection: %s" % str(e)) - def _httpRequest(self, method, path, post_data = None, files = None): + def _httpGet(self, path): + return requests.get("http://" + self._address + self._api_prefix + path) + + def _httpPost(self, path, file_data): with self._http_lock: - self._http_connection = httpclient.HTTPConnection(self._address, timeout = 30) - try: - if files is not None: - boundary = 'wL36Yn8afVp8Ag7AmP8qZ0SA4n1v9T' - s = HttpUploadDataStream.HttpUploadDataStream() - for k, v in files.items(): - filename = v[0] - file_contents = v[1] - s.write('--%s\r\n' % (boundary)) - s.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (k, filename)) - s.write('Content-Type: application/octet-stream\r\n') - s.write('Content-Transfer-Encoding: binary\r\n') - s.write('\r\n') + files_dict = {} + if isinstance(file_data, list): # in case a list with strings is sent + single_string_file_data = "" + for line in file_data: + single_string_file_data += line + files_dict = {"file":("test.gcode", single_string_file_data)} + else: + files_dict = {"file":("test.gcode", file_data)} - if file_contents is not None: - for line in file_contents: - s.write(str(line)) - - s.write('\r\n') - - for k, v in post_data.items(): - s.write('--%s\r\n' % (boundary)) - s.write('Content-Disposition: form-data; name="%s"\r\n' % (k)) - s.write('\r\n') - s.write(str(v)) - s.write('\r\n') - s.write('--%s--\r\n' % (boundary)) - - self._http_connection.request(method, path, s, {"Content-type": "multipart/form-data; boundary=%s" % (boundary), "Content-Length": len(s)}) - elif post_data is not None: - - self._http_connection.request(method, path, urllib.urlencode(post_data), {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"}) - else: - self._http_connection.request(method, path, headers={"Content-type": "application/x-www-form-urlencoded", "User-Agent": "Cura Doodle3D connection"}) - except IOError: - self._http_connection.close() - return None - except Exception as e: - self._http_connection.close() - return None - - try: - response = self._http_connection.getresponse() - response_text = response.read() - except IOError: - self._http_connection.close() - return None - try: - response = json.loads(response_text.decode("utf-8")) - except ValueError: - self._http_connection.close() - return None - self._http_connection.close() - return response + return requests.post("http://" + self._address + self._api_prefix + path, files = files_dict) \ No newline at end of file diff --git a/WifiOutputDevicePlugin.py b/WifiOutputDevicePlugin.py index e01c078cde..e9b1848138 100644 --- a/WifiOutputDevicePlugin.py +++ b/WifiOutputDevicePlugin.py @@ -14,24 +14,24 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): addConnectionSignal = Signal() + ## Start looking for devices on network. def start(self): self._browser = ServiceBrowser(Zeroconf(), u'_ultimaker._tcp.local.', [self._onServiceChanged]) + ## Stop looking for devices on network. def stop(self): self._zero_conf.close() ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addConnection(self, name, address, properties): - connection = WifiConnection.WifiConnection(address, properties) - connection.connect() - self._connections[address] = connection - if address == "10.180.1.23": #DEBUG - #if address == "10.180.0.249": #DEBUG - connection.startPrint() + if address == "10.180.1.30": #DEBUG + connection = WifiConnection.WifiConnection(address, properties) + connection.connect() + self._connections[address] = connection connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) def _onPrinterConnectionStateChanged(self, address): - print(self._connections[address].isConnected()) + print("_onPrinterConnectionStateChanged" , self._connections[address].isConnected()) if self._connections[address].isConnected(): self.getOutputDeviceManager().addOutputDevice(self._connections[address]) else: @@ -49,5 +49,6 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): self.addConnectionSignal.emit(str(name), address, info.properties) elif state_change == ServiceStateChange.Removed: - print("Device disconnected") - #print("HERP DERP") + info = zeroconf.get_service_info(service_type, name) + address = '.'.join(map(lambda n: str(n), info.address)) + print("Device disconnected: ", address) From 69a9ef4a64dca2c12338942f931a8d0e644c5a07 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 11 Feb 2016 11:18:27 +0100 Subject: [PATCH 004/297] Removed debug prints CURA-49 --- WifiOutputDevicePlugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/WifiOutputDevicePlugin.py b/WifiOutputDevicePlugin.py index e9b1848138..f4294c094c 100644 --- a/WifiOutputDevicePlugin.py +++ b/WifiOutputDevicePlugin.py @@ -31,7 +31,6 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) def _onPrinterConnectionStateChanged(self, address): - print("_onPrinterConnectionStateChanged" , self._connections[address].isConnected()) if self._connections[address].isConnected(): self.getOutputDeviceManager().addOutputDevice(self._connections[address]) else: @@ -51,4 +50,3 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): elif state_change == ServiceStateChange.Removed: info = zeroconf.get_service_info(service_type, name) address = '.'.join(map(lambda n: str(n), info.address)) - print("Device disconnected: ", address) From 10b39c5ca4db83bc0cc424f5c2ddd9892d554273 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 11 Feb 2016 15:51:40 +0100 Subject: [PATCH 005/297] Added keys to wificonnection so they can be linked to machine instances CURA-49 --- WifiConnection.py | 26 ++++++++++++++++---------- WifiOutputDevicePlugin.py | 23 +++++++++++++++++------ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/WifiConnection.py b/WifiConnection.py index 535971e840..996cb2e5ea 100644 --- a/WifiConnection.py +++ b/WifiConnection.py @@ -14,9 +14,10 @@ from UM.Logger import Logger i18n_catalog = i18nCatalog("cura") class WifiConnection(OutputDevice, SignalEmitter): - def __init__(self, address, info): - super().__init__(address) + def __init__(self, key, address, info): + super().__init__(key) self._address = address + self._key = key self._info = info self._http_lock = threading.Lock() self._http_connection = None @@ -30,14 +31,16 @@ class WifiConnection(OutputDevice, SignalEmitter): self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" - self.connect() - self.setName(address) + self.setName(key) self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print with WIFI")) self.setDescription(i18n_catalog.i18nc("@info:tooltip", "Print with WIFI")) self.setIconName("print") connectionStateChanged = Signal() + def getKey(self): + return self._key + def isConnected(self): return self._is_connected @@ -54,12 +57,15 @@ class WifiConnection(OutputDevice, SignalEmitter): def _update(self): while self._thread: self.setConnectionState(True) - reply = self._httpGet("printer") - if reply.status_code == 200: - self._json_printer_state = reply.json() - if not self._is_connected: - self.setConnectionState(True) - else: + try: + reply = self._httpGet("printer") + if reply.status_code == 200: + self._json_printer_state = reply.json() + if not self._is_connected: + self.setConnectionState(True) + else: + self.setConnectionState(False) + except: self.setConnectionState(False) time.sleep(1) diff --git a/WifiOutputDevicePlugin.py b/WifiOutputDevicePlugin.py index f4294c094c..7b89002a43 100644 --- a/WifiOutputDevicePlugin.py +++ b/WifiOutputDevicePlugin.py @@ -3,6 +3,7 @@ from . import WifiConnection from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange from UM.Signal import Signal, SignalEmitter +from UM.Application import Application class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def __init__(self): @@ -11,7 +12,7 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): self._browser = None self._connections = {} self.addConnectionSignal.connect(self.addConnection) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. - + Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineInstanceChanged) addConnectionSignal = Signal() ## Start looking for devices on network. @@ -22,13 +23,23 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def stop(self): self._zero_conf.close() + def _onActiveMachineInstanceChanged(self): + active_machine_key = Application.getInstance().getMachineManager().getActiveMachineInstance().getKey() + for address in self._connections: + if self._connections[address].getKey() == active_machine_key: + self._connections[address].connect() + self._connections[address].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + else: + self._connections[address].close() + print("on active machine instance changed" , active_machine_key) + ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addConnection(self, name, address, properties): - if address == "10.180.1.30": #DEBUG - connection = WifiConnection.WifiConnection(address, properties) - connection.connect() - self._connections[address] = connection - connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + connection = WifiConnection.WifiConnection(name, address, properties) + self._connections[address] = connection + if connection.getKey() == Application.getInstance().getMachineManager().getActiveMachineInstance().getKey(): + self._connections[address].connect() + connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) def _onPrinterConnectionStateChanged(self, address): if self._connections[address].isConnected(): From 3bd25e5f7ff82e1ca167f464c8561a7f42296ade Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 18 Apr 2016 16:16:59 +0200 Subject: [PATCH 006/297] NetworkPrinterOutputDevice now uses reworked printerOutputDevice API CURA-49 --- ...ection.py => NetworkPrinterOutputDevice.py | 41 +++++-------------- WifiOutputDevicePlugin.py | 7 ++-- 2 files changed, 14 insertions(+), 34 deletions(-) rename WifiConnection.py => NetworkPrinterOutputDevice.py (68%) diff --git a/WifiConnection.py b/NetworkPrinterOutputDevice.py similarity index 68% rename from WifiConnection.py rename to NetworkPrinterOutputDevice.py index 996cb2e5ea..de5a5961fd 100644 --- a/WifiConnection.py +++ b/NetworkPrinterOutputDevice.py @@ -1,19 +1,18 @@ -from UM.OutputDevice.OutputDevice import OutputDevice -from UM.OutputDevice import OutputDeviceError import threading -import json import time -import base64 import requests -from . import HttpUploadDataStream from UM.i18n import i18nCatalog -from UM.Signal import Signal, SignalEmitter +from UM.Signal import Signal from UM.Application import Application from UM.Logger import Logger + +from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState + i18n_catalog = i18nCatalog("cura") -class WifiConnection(OutputDevice, SignalEmitter): + +class NetworkPrinterOutputDevice(PrinterOutputDevice): def __init__(self, key, address, info): super().__init__(key) self._address = address @@ -27,8 +26,6 @@ class WifiConnection(OutputDevice, SignalEmitter): self._json_printer_state = None - self._is_connected = False - self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" self.setName(key) @@ -36,38 +33,20 @@ class WifiConnection(OutputDevice, SignalEmitter): self.setDescription(i18n_catalog.i18nc("@info:tooltip", "Print with WIFI")) self.setIconName("print") - connectionStateChanged = Signal() - def getKey(self): return self._key - def isConnected(self): - return self._is_connected - - ## Set the connection state of this connection. - # Although we use a restfull API, we do poll the api to check if the machine is still responding. - def setConnectionState(self, state): - if state != self._is_connected: - Logger.log("i", "setting connection state of %s to %s " %(self._address, state)) - self._is_connected = state - self.connectionStateChanged.emit(self._address) - else: - self._is_connected = state - def _update(self): - while self._thread: - self.setConnectionState(True) + while self._connection_state == ConnectionState.connected or self._connection_state == ConnectionState.busy: try: reply = self._httpGet("printer") if reply.status_code == 200: self._json_printer_state = reply.json() - if not self._is_connected: - self.setConnectionState(True) else: - self.setConnectionState(False) + self.setConnectionState(ConnectionState.error) except: - self.setConnectionState(False) - time.sleep(1) + self.setConnectionState(ConnectionState.error) + time.sleep(1) # Poll every second for printer state. def close(self): self._do_update = False diff --git a/WifiOutputDevicePlugin.py b/WifiOutputDevicePlugin.py index 7b89002a43..8d4ea516e8 100644 --- a/WifiOutputDevicePlugin.py +++ b/WifiOutputDevicePlugin.py @@ -1,5 +1,5 @@ from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin -from . import WifiConnection +from . import NetworkPrinterOutputDevice from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange from UM.Signal import Signal, SignalEmitter @@ -35,7 +35,7 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addConnection(self, name, address, properties): - connection = WifiConnection.WifiConnection(name, address, properties) + connection = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) self._connections[address] = connection if connection.getKey() == Application.getInstance().getMachineManager().getActiveMachineInstance().getKey(): self._connections[address].connect() @@ -60,4 +60,5 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): elif state_change == ServiceStateChange.Removed: info = zeroconf.get_service_info(service_type, name) - address = '.'.join(map(lambda n: str(n), info.address)) + if info: + address = '.'.join(map(lambda n: str(n), info.address)) From 960ac9af9961932f7b95dd284d917b1863265a3d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 18 Apr 2016 16:31:14 +0200 Subject: [PATCH 007/297] Close now uses new API stuff --- NetworkPrinterOutputDevice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index de5a5961fd..e67826eff5 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -49,11 +49,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): time.sleep(1) # Poll every second for printer state. def close(self): - self._do_update = False - self._is_connected = False + self._connection_state == ConnectionState.closed + self._thread = None def requestWrite(self, node, file_name = None): - self._file = getattr( Application.getInstance().getController().getScene(), "gcode_list") + self._file = getattr(Application.getInstance().getController().getScene(), "gcode_list") self.startPrint() ## Start the polling thread. @@ -79,7 +79,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _httpPost(self, path, file_data): with self._http_lock: files_dict = {} - if isinstance(file_data, list): # in case a list with strings is sent + if isinstance(file_data, list): # in case a list with strings is sent single_string_file_data = "" for line in file_data: single_string_file_data += line From 4817c05a522634518d9e877c948b13f6fc85a786 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 18 Apr 2016 16:32:02 +0200 Subject: [PATCH 008/297] Renamed plugin --- ...tputDevicePlugin.py => NetworkPrinterOutputDevicePlugin.py | 0 __init__.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename WifiOutputDevicePlugin.py => NetworkPrinterOutputDevicePlugin.py (100%) diff --git a/WifiOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py similarity index 100% rename from WifiOutputDevicePlugin.py rename to NetworkPrinterOutputDevicePlugin.py diff --git a/__init__.py b/__init__.py index f06ca20d57..6c3a0c3cc1 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from . import WifiOutputDevicePlugin +from . import NetworkPrinterOutputDevicePlugin from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -16,5 +16,5 @@ def getMetaData(): } def register(app): - return { "output_device": WifiOutputDevicePlugin.WifiOutputDevicePlugin() } + return { "output_device": NetworkPrinterOutputDevicePlugin.WifiOutputDevicePlugin()} From d621162a9bc62fdf77d0fe1997564aaf38d031c0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 18 Apr 2016 16:36:52 +0200 Subject: [PATCH 009/297] Codestyle --- NetworkPrinterOutputDevicePlugin.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 8d4ea516e8..5f0352ff3e 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -5,14 +5,18 @@ from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange from UM.Signal import Signal, SignalEmitter from UM.Application import Application + class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def __init__(self): super().__init__() self._zero_conf = Zeroconf() self._browser = None self._connections = {} - self.addConnectionSignal.connect(self.addConnection) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. + + # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. + self.addConnectionSignal.connect(self.addConnection) Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineInstanceChanged) + addConnectionSignal = Signal() ## Start looking for devices on network. @@ -31,7 +35,6 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): self._connections[address].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) else: self._connections[address].close() - print("on active machine instance changed" , active_machine_key) ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addConnection(self, name, address, properties): From 0ad1c1a3a70017fb4e72b89c1f24204e466aab55 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 09:48:21 +0200 Subject: [PATCH 010/297] Renamed connections to printers CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 5f0352ff3e..3b281c3984 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -11,7 +11,7 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): super().__init__() self._zero_conf = Zeroconf() self._browser = None - self._connections = {} + self._printers = {} # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addConnectionSignal.connect(self.addConnection) @@ -29,26 +29,26 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def _onActiveMachineInstanceChanged(self): active_machine_key = Application.getInstance().getMachineManager().getActiveMachineInstance().getKey() - for address in self._connections: - if self._connections[address].getKey() == active_machine_key: - self._connections[address].connect() - self._connections[address].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + for address in self._printers: + if self._printers[address].getKey() == active_machine_key: + self._printers[address].connect() + self._printers[address].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) else: - self._connections[address].close() + self._printers[address].close() ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addConnection(self, name, address, properties): connection = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) - self._connections[address] = connection + self._printers[address] = connection if connection.getKey() == Application.getInstance().getMachineManager().getActiveMachineInstance().getKey(): - self._connections[address].connect() + self._printers[address].connect() connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) def _onPrinterConnectionStateChanged(self, address): - if self._connections[address].isConnected(): - self.getOutputDeviceManager().addOutputDevice(self._connections[address]) + if self._printers[address].isConnected(): + self.getOutputDeviceManager().addOutputDevice(self._printers[address]) else: - self.getOutputDeviceManager().removeOutputDevice(self._connections[address]) + self.getOutputDeviceManager().removeOutputDevice(self._printers[address]) def removeConnection(self): pass From f488d8eb28cea1156d1793e842ca421c56769cd1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 09:51:32 +0200 Subject: [PATCH 011/297] Zero conf browser now uses stored zeroconf obj CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 3b281c3984..87a0672484 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -21,9 +21,9 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): ## Start looking for devices on network. def start(self): - self._browser = ServiceBrowser(Zeroconf(), u'_ultimaker._tcp.local.', [self._onServiceChanged]) + self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged]) - ## Stop looking for devices on network. + ## Stop looking for devices on network.s def stop(self): self._zero_conf.close() From 2a90c76cb879aaaa5bbb12b5307fe688f53b2672 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 09:57:31 +0200 Subject: [PATCH 012/297] Renamed connection to printer CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 87a0672484..84f8b89097 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -14,10 +14,10 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): self._printers = {} # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. - self.addConnectionSignal.connect(self.addConnection) + self.addPrinterSignal.connect(self.addPrinter) Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineInstanceChanged) - addConnectionSignal = Signal() + addPrinterSignal = Signal() ## Start looking for devices on network. def start(self): @@ -37,12 +37,12 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): self._printers[address].close() ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. - def addConnection(self, name, address, properties): - connection = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) - self._printers[address] = connection - if connection.getKey() == Application.getInstance().getMachineManager().getActiveMachineInstance().getKey(): + def addPrinter(self, name, address, properties): + printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) + self._printers[address] = printer + if printer.getKey() == Application.getInstance().getMachineManager().getActiveMachineInstance().getKey(): self._printers[address].connect() - connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) def _onPrinterConnectionStateChanged(self, address): if self._printers[address].isConnected(): @@ -50,7 +50,7 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): else: self.getOutputDeviceManager().removeOutputDevice(self._printers[address]) - def removeConnection(self): + def removePrinter(self): pass def _onServiceChanged(self, zeroconf, service_type, name, state_change): @@ -59,7 +59,7 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): if info: if info.properties.get(b"type", None): address = '.'.join(map(lambda n: str(n), info.address)) - self.addConnectionSignal.emit(str(name), address, info.properties) + self.addPrinterSignal.emit(str(name), address, info.properties) elif state_change == ServiceStateChange.Removed: info = zeroconf.get_service_info(service_type, name) From 404ea89ff778a86a861d641c73422ee328946f3d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 10:51:24 +0200 Subject: [PATCH 013/297] Connection now correctly uses connection state CURA-49 --- NetworkPrinterOutputDevice.py | 21 ++++++++++++++++----- NetworkPrinterOutputDevicePlugin.py | 22 +++++++++++----------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e67826eff5..bd6e8f77f8 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -3,7 +3,6 @@ import time import requests from UM.i18n import i18nCatalog -from UM.Signal import Signal from UM.Application import Application from UM.Logger import Logger @@ -11,7 +10,7 @@ from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState i18n_catalog = i18nCatalog("cura") - +## Network connected (wifi / lan) printer that uses the Ultimaker API class NetworkPrinterOutputDevice(PrinterOutputDevice): def __init__(self, key, address, info): super().__init__(key) @@ -21,7 +20,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._http_lock = threading.Lock() self._http_connection = None self._file = None - self._do_update = True self._thread = None self._json_printer_state = None @@ -37,16 +35,26 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return self._key def _update(self): - while self._connection_state == ConnectionState.connected or self._connection_state == ConnectionState.busy: + Logger.log("d", "Update thread of printer with key %s and ip %s started", self._key, self._address) + while self.isConnected(): try: reply = self._httpGet("printer") if reply.status_code == 200: self._json_printer_state = reply.json() + if self._connection_state == ConnectionState.connecting: + # First successful response, so we are now "connected" + self.setConnectionState(ConnectionState.connected) else: self.setConnectionState(ConnectionState.error) except: self.setConnectionState(ConnectionState.error) time.sleep(1) # Poll every second for printer state. + Logger.log("d", "Update thread of printer with key %s and ip %s stopped", self._key, self._address) + + ## Convenience function that gets information from the recieved json data and converts it to the right internal + # values / variables + def _spliceJsonData(self): + pass def close(self): self._connection_state == ConnectionState.closed @@ -56,10 +64,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._file = getattr(Application.getInstance().getController().getScene(), "gcode_list") self.startPrint() + def isConnected(self): + return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error + ## Start the polling thread. def connect(self): if self._thread is None: - self._do_update = True + self.setConnectionState(ConnectionState.connecting) self._thread = threading.Thread(target = self._update) self._thread.daemon = True self._thread.start() diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 84f8b89097..cd8d6a3742 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -29,26 +29,26 @@ class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def _onActiveMachineInstanceChanged(self): active_machine_key = Application.getInstance().getMachineManager().getActiveMachineInstance().getKey() - for address in self._printers: - if self._printers[address].getKey() == active_machine_key: - self._printers[address].connect() - self._printers[address].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + for key in self._printers: + if key == active_machine_key: + self._printers[key].connect() + self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) else: - self._printers[address].close() + self._printers[key].close() ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addPrinter(self, name, address, properties): printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) - self._printers[address] = printer + self._printers[printer.getKey()] = printer if printer.getKey() == Application.getInstance().getMachineManager().getActiveMachineInstance().getKey(): - self._printers[address].connect() + self._printers[printer.getKey()].connect() printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) - def _onPrinterConnectionStateChanged(self, address): - if self._printers[address].isConnected(): - self.getOutputDeviceManager().addOutputDevice(self._printers[address]) + def _onPrinterConnectionStateChanged(self, key): + if self._printers[key].isConnected(): + self.getOutputDeviceManager().addOutputDevice(self._printers[key]) else: - self.getOutputDeviceManager().removeOutputDevice(self._printers[address]) + self.getOutputDeviceManager().removeOutputDevice(self._printers[key]) def removePrinter(self): pass From 48625ed129d190eecedba19afe7f2a10f0d6aaf1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 11:02:55 +0200 Subject: [PATCH 014/297] Improved error logging & handling in update thread CURA-49 --- NetworkPrinterOutputDevice.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index bd6e8f77f8..37964254ce 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -24,6 +24,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._json_printer_state = None + self._num_extruders = 2 + self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" self.setName(key) @@ -41,19 +43,26 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): reply = self._httpGet("printer") if reply.status_code == 200: self._json_printer_state = reply.json() + try: + self._spliceJSONData() + except: + # issues with json parsing should not break by definition TODO: Check in what cases it should fail. + pass if self._connection_state == ConnectionState.connecting: # First successful response, so we are now "connected" self.setConnectionState(ConnectionState.connected) else: + Logger.log("w", "Got http status code %s while trying to request data.", reply.status_code) self.setConnectionState(ConnectionState.error) - except: + except Exception as e: self.setConnectionState(ConnectionState.error) - time.sleep(1) # Poll every second for printer state. - Logger.log("d", "Update thread of printer with key %s and ip %s stopped", self._key, self._address) + Logger.log("w", "Exception occured while connecting; %s", str(e)) + time.sleep(2) # Poll every second for printer state. + Logger.log("d", "Update thread of printer with key %s and ip %s stopped with state: %s", self._key, self._address, self._connection_state) ## Convenience function that gets information from the recieved json data and converts it to the right internal # values / variables - def _spliceJsonData(self): + def _spliceJSONData(self): pass def close(self): From 298c740abe3b7566ec846237ccd3f7370f0728b7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 11:06:59 +0200 Subject: [PATCH 015/297] Bed & extruder temps are now logged CURA-49 --- NetworkPrinterOutputDevice.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 37964254ce..2c8e7acb20 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -63,7 +63,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Convenience function that gets information from the recieved json data and converts it to the right internal # values / variables def _spliceJSONData(self): - pass + # Check for hotend temperatures + for index in range(0, self._num_extruders - 1): + temperature = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["temperature"]["current"] + self._setHotendTemperature(index, temperature) + + bed_temperature = self._json_printer_state["bed"]["temperature"] + self._setBedTemperature(bed_temperature) def close(self): self._connection_state == ConnectionState.closed From cf061b2fbef41ffda1a0a01a8f4f239bd6005b05 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 11:42:39 +0200 Subject: [PATCH 016/297] Added filter machines to requestWrite CURA-49 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2c8e7acb20..989b4996f9 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -75,7 +75,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_state == ConnectionState.closed self._thread = None - def requestWrite(self, node, file_name = None): + def requestWrite(self, node, file_name = None, filter_by_machine = False): self._file = getattr(Application.getInstance().getController().getScene(), "gcode_list") self.startPrint() From 94ed8c8177889b8aa3a1d30bafce94fb7655bc37 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 11:44:51 +0200 Subject: [PATCH 017/297] UpdateThread now correctly uses join upon close CURA-49 --- NetworkPrinterOutputDevice.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 989b4996f9..84018f0a91 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -20,7 +20,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._http_lock = threading.Lock() self._http_connection = None self._file = None - self._thread = None + self._update_thread = None self._json_printer_state = None @@ -73,7 +73,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def close(self): self._connection_state == ConnectionState.closed - self._thread = None + self._update_thread.join() + self._update_thread = None def requestWrite(self, node, file_name = None, filter_by_machine = False): self._file = getattr(Application.getInstance().getController().getScene(), "gcode_list") @@ -84,11 +85,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Start the polling thread. def connect(self): - if self._thread is None: + if self._update_thread is None: self.setConnectionState(ConnectionState.connecting) - self._thread = threading.Thread(target = self._update) - self._thread.daemon = True - self._thread.start() + self._update_thread = threading.Thread(target = self._update) + self._update_thread.daemon = True + self._update_thread.start() def getCameraImage(self): pass #Do Nothing From 688ab85dc6676ff7cfe29901536c3b66fd252079 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 11:59:02 +0200 Subject: [PATCH 018/297] Added status messages when printing with network CURA-49 --- NetworkPrinterOutputDevice.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 84018f0a91..a385cdf197 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -6,6 +6,8 @@ from UM.i18n import i18nCatalog from UM.Application import Application from UM.Logger import Logger +from UM.Message import Message + from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState i18n_catalog = i18nCatalog("cura") @@ -33,6 +35,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setDescription(i18n_catalog.i18nc("@info:tooltip", "Print with WIFI")) self.setIconName("print") + self._progress_message = None + self._error_message = None + def getKey(self): return self._key @@ -96,8 +101,19 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def startPrint(self): try: + self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) + self._progress_message.show() + #TODO: Create a job that handles this! (As it currently locks up UI) result = self._httpPost("print_job", self._file) + self._progress_message.hide() + if result.status_code == 200: + pass + except IOError: + self._progress_message.hide() + self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to send data printer. Is another job still active?")) + self._error_message.show() except Exception as e: + self._progress_message.hide() Logger.log("e" , "An exception occured in wifi connection: %s" % str(e)) def _httpGet(self, path): From 2034aeb5c17e5b624ea41cb3f645ebf5736cae50 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 12:05:03 +0200 Subject: [PATCH 019/297] Progress of a print job is now tracked CURA-49 --- NetworkPrinterOutputDevice.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index a385cdf197..5bcbcea9aa 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -45,9 +45,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("d", "Update thread of printer with key %s and ip %s started", self._key, self._address) while self.isConnected(): try: - reply = self._httpGet("printer") - if reply.status_code == 200: - self._json_printer_state = reply.json() + printer_reply = self._httpGet("printer") + if printer_reply.status_code == 200: + self._json_printer_state = printer_reply.json() try: self._spliceJSONData() except: @@ -57,8 +57,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # First successful response, so we are now "connected" self.setConnectionState(ConnectionState.connected) else: - Logger.log("w", "Got http status code %s while trying to request data.", reply.status_code) + Logger.log("w", "Got http status code %s while trying to request data.", printer_reply.status_code) self.setConnectionState(ConnectionState.error) + + print_job_reply = self._httpGet("print_job") + if print_job_reply.status_code != 404: + self.setProgress(print_job_reply.json()["progress"]) + else: + self.setProgress(0) + + except Exception as e: self.setConnectionState(ConnectionState.error) Logger.log("w", "Exception occured while connecting; %s", str(e)) From 1a44c394e10e341e93dd3ddfed792f7161e4143f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 12:10:53 +0200 Subject: [PATCH 020/297] Warning message is shown if the printer is still active. CURA-49 --- NetworkPrinterOutputDevice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 5bcbcea9aa..f17ea99143 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -108,6 +108,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass #Do Nothing def startPrint(self): + if self._progress != 0: + self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) + self._error_message.show() + return try: self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) self._progress_message.show() @@ -118,7 +122,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass except IOError: self._progress_message.hide() - self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to send data printer. Is another job still active?")) + self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to send data to printer. Is another job still active?")) self._error_message.show() except Exception as e: self._progress_message.hide() From 387dae140ff50679921c19d60cfb0e5ccb36d35a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 12:15:45 +0200 Subject: [PATCH 021/297] UpdateThread now only joins when it's actually running An output device can be created and never have it's connect called. Because the delete of the output device calls close, we need to handle this CURA-49 --- NetworkPrinterOutputDevice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f17ea99143..21a5e14672 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -65,7 +65,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setProgress(print_job_reply.json()["progress"]) else: self.setProgress(0) - except Exception as e: self.setConnectionState(ConnectionState.error) @@ -86,8 +85,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def close(self): self._connection_state == ConnectionState.closed - self._update_thread.join() - self._update_thread = None + if self._update_thread != None: + self._update_thread.join() + self._update_thread = None def requestWrite(self, node, file_name = None, filter_by_machine = False): self._file = getattr(Application.getInstance().getController().getScene(), "gcode_list") From f9bf54348dcea088ec25eb9e037e38284280329d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 19 Apr 2016 15:35:56 +0200 Subject: [PATCH 022/297] Head position is now saved CURA-49 --- NetworkPrinterOutputDevice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 21a5e14672..92a68b9d4c 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -83,6 +83,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): bed_temperature = self._json_printer_state["bed"]["temperature"] self._setBedTemperature(bed_temperature) + head_x = self._json_printer_state["heads"][0]["position"]["x"] + head_y = self._json_printer_state["heads"][0]["position"]["y"] + head_z = self._json_printer_state["heads"][0]["position"]["z"] + self._updateHeadPosition(head_x, head_y, head_z) + def close(self): self._connection_state == ConnectionState.closed if self._update_thread != None: From c700a684d3cf8ab6b8dd99097fee4c26b618a931 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 21 Apr 2016 15:57:26 +0200 Subject: [PATCH 023/297] We now use QT stuff for uploading, as this doesn't mess up the GIL CURA-49 --- NetworkPrinterOutputDevice.py | 59 ++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 92a68b9d4c..07e175dced 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -8,8 +8,13 @@ from UM.Logger import Logger from UM.Message import Message +from .SendGCodeJob import SendGCodeJob + from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState +from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager +from PyQt5.QtCore import QUrl + i18n_catalog = i18nCatalog("cura") ## Network connected (wifi / lan) printer that uses the Ultimaker API @@ -35,6 +40,19 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setDescription(i18n_catalog.i18nc("@info:tooltip", "Print with WIFI")) self.setIconName("print") + self._manager = QNetworkAccessManager() + self._manager.finished.connect(self._onFinished) + + ## Hack to ensure that the qt networking stuff isn't garbage collected (unless we want it to) + self._qt_request = None + self._qt_reply = None + self._qt_multi_part = None + self._qt_part = None + + + #request_qt_get = QNetworkRequest(QUrl("http://10.180.0.53/api/v1/printer")) + #response = self._manager.get(request_qt_get) + self._progress_message = None self._error_message = None @@ -120,11 +138,34 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): try: self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) self._progress_message.show() - #TODO: Create a job that handles this! (As it currently locks up UI) - result = self._httpPost("print_job", self._file) - self._progress_message.hide() - if result.status_code == 200: - pass + + single_string_file_data = "" + for line in self._file: + single_string_file_data += line + + ## TODO: Use correct file name (we use placeholder now) + file_name = "test.gcode" + + ## Create multi_part request + self._qt_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) + + ## Create part (to be placed inside multipart) + self._qt_part = QHttpPart() + self._qt_part.setHeader(QNetworkRequest.ContentDispositionHeader, + "form-data; name=\"file\"; filename=\"%s\"" % file_name) + self._qt_part.setBody(single_string_file_data) + self._qt_multi_part.append(self._qt_part) + + url = "http://" + self._address + self._api_prefix + "print_job" + + url_2 = "http://10.180.0.53/api/v1/print_job" + ## Create the QT request + self._qt_request = QNetworkRequest(QUrl("http://10.180.0.53/api/v1/print_job")) + + ## Post request + data + self._qt_reply = self._manager.post(self._qt_request, self._qt_multi_part) + self._qt_reply.uploadProgress.connect(self._onUploadProgress) + except IOError: self._progress_message.hide() self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to send data to printer. Is another job still active?")) @@ -133,6 +174,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message.hide() Logger.log("e" , "An exception occured in wifi connection: %s" % str(e)) + def _onFinished(self, reply): + #print(reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) + reply.uploadProgress.disconnect(self._onUploadProgress) + self._progress_message.hide() + + def _onUploadProgress(self, bytes_sent, bytes_total): + self._progress_message.setProgress(bytes_sent / bytes_total * 100) + def _httpGet(self, path): return requests.get("http://" + self._address + self._api_prefix + path) From 27b3fa1d45e4a8382bad60bcbd80a77d841c2f63 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 10:20:46 +0200 Subject: [PATCH 024/297] Removed send-gcode job (no longer need job because we use qt sending) CURA-49 --- NetworkPrinterOutputDevice.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 07e175dced..8defed6bc4 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -8,8 +8,6 @@ from UM.Logger import Logger from UM.Message import Message -from .SendGCodeJob import SendGCodeJob - from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager From bc8f8473e566b57e9d4d848a58728b84780d7961 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 10:22:06 +0200 Subject: [PATCH 025/297] Name refactor CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 2 +- __init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index cd8d6a3742..ac0c55a0b0 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -6,7 +6,7 @@ from UM.Signal import Signal, SignalEmitter from UM.Application import Application -class WifiOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): +class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def __init__(self): super().__init__() self._zero_conf = Zeroconf() diff --git a/__init__.py b/__init__.py index 6c3a0c3cc1..b5aaa03513 100644 --- a/__init__.py +++ b/__init__.py @@ -16,5 +16,5 @@ def getMetaData(): } def register(app): - return { "output_device": NetworkPrinterOutputDevicePlugin.WifiOutputDevicePlugin()} + return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin()} From 41a07c000d9bbe778ca5f5e97d604267bdf4e6f3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 10:24:15 +0200 Subject: [PATCH 026/297] Added extra status catching for when active machine instance is None CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index ac0c55a0b0..b4e2b6a014 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -28,7 +28,11 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): self._zero_conf.close() def _onActiveMachineInstanceChanged(self): - active_machine_key = Application.getInstance().getMachineManager().getActiveMachineInstance().getKey() + try: + active_machine_key = Application.getInstance().getMachineManager().getActiveMachineInstance().getKey() + except AttributeError: + ## Active machine instance changed to None. This can happen upon clean start. Simply ignore. + return for key in self._printers: if key == active_machine_key: self._printers[key].connect() From 8b764c7585741575ad24565f3b705dc21bfce54a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 11:39:31 +0200 Subject: [PATCH 027/297] Code cleanup & documentation CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index b4e2b6a014..6e674b289d 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -6,6 +6,9 @@ from UM.Signal import Signal, SignalEmitter from UM.Application import Application +## This plugin handles the connection detection & creation of output device objects for the UM3 printer. +# Zero-Conf is used to detect printers, which are saved in a dict. +# If we discover a printer that has the same key as the active machine instance a connection is made. class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def __init__(self): super().__init__() @@ -23,7 +26,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def start(self): self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged]) - ## Stop looking for devices on network.s + ## Stop looking for devices on network. def stop(self): self._zero_conf.close() @@ -48,15 +51,14 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): self._printers[printer.getKey()].connect() printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + ## Handler for when the connection state of one of the detected printers changes def _onPrinterConnectionStateChanged(self, key): if self._printers[key].isConnected(): self.getOutputDeviceManager().addOutputDevice(self._printers[key]) else: self.getOutputDeviceManager().removeOutputDevice(self._printers[key]) - def removePrinter(self): - pass - + ## Handler for zeroConf detection def _onServiceChanged(self, zeroconf, service_type, name, state_change): if state_change == ServiceStateChange.Added: info = zeroconf.get_service_info(service_type, name) @@ -66,6 +68,6 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): self.addPrinterSignal.emit(str(name), address, info.properties) elif state_change == ServiceStateChange.Removed: - info = zeroconf.get_service_info(service_type, name) - if info: - address = '.'.join(map(lambda n: str(n), info.address)) + pass + # TODO; This isn't testable right now. We need to also decide how to handle + # \ No newline at end of file From 982258ece7b30b7a46cd36521184a9779aae1e6a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 11:42:13 +0200 Subject: [PATCH 028/297] Cleaned up more code CURA-49 --- NetworkPrinterOutputDevice.py | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 8defed6bc4..6dcc6faf6c 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -15,6 +15,7 @@ from PyQt5.QtCore import QUrl i18n_catalog = i18nCatalog("cura") + ## Network connected (wifi / lan) printer that uses the Ultimaker API class NetworkPrinterOutputDevice(PrinterOutputDevice): def __init__(self, key, address, info): @@ -47,10 +48,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._qt_multi_part = None self._qt_part = None - - #request_qt_get = QNetworkRequest(QUrl("http://10.180.0.53/api/v1/printer")) - #response = self._manager.get(request_qt_get) - self._progress_message = None self._error_message = None @@ -126,7 +123,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_thread.start() def getCameraImage(self): - pass #Do Nothing + pass # TODO: This still needs to be implemented (we don't have a place to show it now anyway) def startPrint(self): if self._progress != 0: @@ -170,10 +167,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._error_message.show() except Exception as e: self._progress_message.hide() - Logger.log("e" , "An exception occured in wifi connection: %s" % str(e)) + Logger.log("e" , "An exception occured in network connection: %s" % str(e)) def _onFinished(self, reply): - #print(reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() @@ -181,17 +177,4 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message.setProgress(bytes_sent / bytes_total * 100) def _httpGet(self, path): - return requests.get("http://" + self._address + self._api_prefix + path) - - def _httpPost(self, path, file_data): - with self._http_lock: - files_dict = {} - if isinstance(file_data, list): # in case a list with strings is sent - single_string_file_data = "" - for line in file_data: - single_string_file_data += line - files_dict = {"file":("test.gcode", single_string_file_data)} - else: - files_dict = {"file":("test.gcode", file_data)} - - return requests.post("http://" + self._address + self._api_prefix + path, files = files_dict) \ No newline at end of file + return requests.get("http://" + self._address + self._api_prefix + path) \ No newline at end of file From bb1a616c1fa825528ce533a60f53d647f4e6129c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 11:44:20 +0200 Subject: [PATCH 029/297] Removed hardcoded url CURA-49 --- NetworkPrinterOutputDevice.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 6dcc6faf6c..0d5fb50a7c 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -151,11 +151,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._qt_part.setBody(single_string_file_data) self._qt_multi_part.append(self._qt_part) - url = "http://" + self._address + self._api_prefix + "print_job" + url = QUrl("http://" + self._address + self._api_prefix + "print_job") - url_2 = "http://10.180.0.53/api/v1/print_job" ## Create the QT request - self._qt_request = QNetworkRequest(QUrl("http://10.180.0.53/api/v1/print_job")) + self._qt_request = QNetworkRequest(url) ## Post request + data self._qt_reply = self._manager.post(self._qt_request, self._qt_multi_part) From 4090e461df300d1f5056238c929b1f9dbbf27f5b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 11:45:16 +0200 Subject: [PATCH 030/297] Setting progress won't cause devision by zero anymore CURA-49 --- NetworkPrinterOutputDevice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 0d5fb50a7c..3faa4dee04 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -173,7 +173,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message.hide() def _onUploadProgress(self, bytes_sent, bytes_total): - self._progress_message.setProgress(bytes_sent / bytes_total * 100) + if bytes_total > 0: + self._progress_message.setProgress(bytes_sent / bytes_total * 100) + else: + self._progress_message.setProgress(0) def _httpGet(self, path): return requests.get("http://" + self._address + self._api_prefix + path) \ No newline at end of file From b3490ee9b99ec77ac2cba94dde1cf07e38c96100 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 12:01:53 +0200 Subject: [PATCH 031/297] Codestyle & documentation CURA-49 --- NetworkPrinterOutputDevice.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 3faa4dee04..5c834876db 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -23,13 +23,15 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._address = address self._key = key self._info = info - self._http_lock = threading.Lock() - self._http_connection = None - self._file = None + + self._gcode = None self._update_thread = None + # This holds the full JSON file that was received from the last request. self._json_printer_state = None + ## Todo: Hardcoded value now; we should probably read this from the machine file. + ## It's okay to leave this for now, as this plugin is um3 only (and has 2 extruders by definition) self._num_extruders = 2 self._api_version = "1" @@ -39,6 +41,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setDescription(i18n_catalog.i18nc("@info:tooltip", "Print with WIFI")) self.setIconName("print") + # QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly + # hook itself into the event loop, which results in events never being fired / done. self._manager = QNetworkAccessManager() self._manager.finished.connect(self._onFinished) @@ -51,6 +55,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message = None self._error_message = None + ## Get the unique key of this machine + # \return key String containing the key of the machine. def getKey(self): return self._key @@ -64,7 +70,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): try: self._spliceJSONData() except: - # issues with json parsing should not break by definition TODO: Check in what cases it should fail. + # issues with json parsing should not break by definition + # TODO: Check in what cases it should fail. pass if self._connection_state == ConnectionState.connecting: # First successful response, so we are now "connected" @@ -82,10 +89,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): except Exception as e: self.setConnectionState(ConnectionState.error) Logger.log("w", "Exception occured while connecting; %s", str(e)) - time.sleep(2) # Poll every second for printer state. + time.sleep(2) # Poll every 2 seconds for printer state. Logger.log("d", "Update thread of printer with key %s and ip %s stopped with state: %s", self._key, self._address, self._connection_state) - ## Convenience function that gets information from the recieved json data and converts it to the right internal + ## Convenience function that gets information from the received json data and converts it to the right internal # values / variables def _spliceJSONData(self): # Check for hotend temperatures @@ -103,12 +110,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def close(self): self._connection_state == ConnectionState.closed - if self._update_thread != None: + if self._update_thread is not None: self._update_thread.join() self._update_thread = None def requestWrite(self, node, file_name = None, filter_by_machine = False): - self._file = getattr(Application.getInstance().getController().getScene(), "gcode_list") + self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") self.startPrint() def isConnected(self): @@ -135,7 +142,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message.show() single_string_file_data = "" - for line in self._file: + for line in self._gcode: single_string_file_data += line ## TODO: Use correct file name (we use placeholder now) From f9a1b75c7c7585828ed0f71cd51546c1c5abacfe Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 15:00:00 +0200 Subject: [PATCH 032/297] Added timeout to get request (so we detect disconnect a lot faster) CURA-49 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 5c834876db..e33105595c 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -186,4 +186,4 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message.setProgress(0) def _httpGet(self, path): - return requests.get("http://" + self._address + self._api_prefix + path) \ No newline at end of file + return requests.get("http://" + self._address + self._api_prefix + path, timeout = 2) \ No newline at end of file From e07386038999638e2051b9b3c58ceea8258ca6f8 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Apr 2016 15:21:44 +0200 Subject: [PATCH 033/297] Devices are now correctly removed CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 6e674b289d..03dadf2144 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -56,7 +56,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): if self._printers[key].isConnected(): self.getOutputDeviceManager().addOutputDevice(self._printers[key]) else: - self.getOutputDeviceManager().removeOutputDevice(self._printers[key]) + self.getOutputDeviceManager().removeOutputDevice(key) ## Handler for zeroConf detection def _onServiceChanged(self, zeroconf, service_type, name, state_change): From fd1d72380b1cd811bc691e589b61209395e03f65 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 28 Apr 2016 09:56:44 +0200 Subject: [PATCH 034/297] Increased timeout of get request CURA-49 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e33105595c..189ba9b095 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -186,4 +186,4 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message.setProgress(0) def _httpGet(self, path): - return requests.get("http://" + self._address + self._api_prefix + path, timeout = 2) \ No newline at end of file + return requests.get("http://" + self._address + self._api_prefix + path, timeout = 5) \ No newline at end of file From 2345289a4e64d0d25859e5e8e1a0e76b94144123 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 12 May 2016 16:43:35 +0200 Subject: [PATCH 035/297] Fix an error when NetworkPrinterOutputDevicePlugin.addPrinter is called before there is an Active Machine Instance CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 03dadf2144..ec367d96d9 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -47,7 +47,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def addPrinter(self, name, address, properties): printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) self._printers[printer.getKey()] = printer - if printer.getKey() == Application.getInstance().getMachineManager().getActiveMachineInstance().getKey(): + active_machine_instance = Application.getInstance().getMachineManager().getActiveMachineInstance() + if active_machine_instance and printer.getKey() == active_machine_instance.getKey(): self._printers[printer.getKey()].connect() printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) From 3f4e740c0dfec9b9adc3aaf3969db43eaead8f7c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 18 May 2016 17:58:15 +0200 Subject: [PATCH 036/297] Add a CMakeLists file so the plugin can be installed --- CMakeLists.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..906af9910c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +project(JediWifiPrintingPlugin) +cmake_minimum_required(VERSION 2.8.12) + +install(FILES + __init__.py + HttpUploadDataStream.py + NetworkPrinterOutputDevice.py + NetworkPrinterOutputDevicePlugin.py + LICENSE + README.md + DESTINATION lib/cura/plugins/JediWifiPrintingPlugin +) From 65f329623f65e9e8f931494f9bb1cfdb9b40d739 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 30 May 2016 15:37:33 +0200 Subject: [PATCH 037/297] Update plugin to API version 3 Contributes to CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 13 +++++++------ __init__.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index ec367d96d9..9e8a43b187 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -18,7 +18,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addPrinterSignal.connect(self.addPrinter) - Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineInstanceChanged) + Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) addPrinterSignal = Signal() @@ -30,12 +30,13 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def stop(self): self._zero_conf.close() - def _onActiveMachineInstanceChanged(self): + def _onGlobalContainerStackChanged(self): try: - active_machine_key = Application.getInstance().getMachineManager().getActiveMachineInstance().getKey() + active_machine_key = Application.getInstance().getGlobalContainerStack().getId() except AttributeError: ## Active machine instance changed to None. This can happen upon clean start. Simply ignore. return + for key in self._printers: if key == active_machine_key: self._printers[key].connect() @@ -47,8 +48,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def addPrinter(self, name, address, properties): printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) self._printers[printer.getKey()] = printer - active_machine_instance = Application.getInstance().getMachineManager().getActiveMachineInstance() - if active_machine_instance and printer.getKey() == active_machine_instance.getKey(): + stack = Application.getInstance().getGlobalContainerStack() + if stack and printer.getKey() == stack.getKey(): self._printers[printer.getKey()].connect() printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) @@ -71,4 +72,4 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): elif state_change == ServiceStateChange.Removed: pass # TODO; This isn't testable right now. We need to also decide how to handle - # \ No newline at end of file + # diff --git a/__init__.py b/__init__.py index b5aaa03513..76d4094709 100644 --- a/__init__.py +++ b/__init__.py @@ -11,7 +11,7 @@ def getMetaData(): "name": "Wifi connection", "author": "Ultimaker", "description": catalog.i18nc("Wifi connection", "Wifi connection"), - "api": 2 + "api": 3 } } From 025bdba516a1eef94d663c76eeb94a519cc80017 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 13 Jun 2016 13:34:22 +0200 Subject: [PATCH 038/297] Changes to make the network plugin play well with setting_rework CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 18 ++++++++---------- __init__.py | 3 +-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 9e8a43b187..fe8629f8d6 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -18,7 +18,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addPrinterSignal.connect(self.addPrinter) - Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) + Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) addPrinterSignal = Signal() @@ -30,15 +30,14 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def stop(self): self._zero_conf.close() - def _onGlobalContainerStackChanged(self): - try: - active_machine_key = Application.getInstance().getGlobalContainerStack().getId() - except AttributeError: - ## Active machine instance changed to None. This can happen upon clean start. Simply ignore. + def _onGlobalStackChanged(self): + + active_machine = Application.getInstance().getGlobalContainerStack() + if not active_machine: return for key in self._printers: - if key == active_machine_key: + if key == active_machine.getKey(): self._printers[key].connect() self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) else: @@ -48,8 +47,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): def addPrinter(self, name, address, properties): printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) self._printers[printer.getKey()] = printer - stack = Application.getInstance().getGlobalContainerStack() - if stack and printer.getKey() == stack.getKey(): + if printer.getKey() == Application.getInstance().getGlobalContainerStack().getKey(): self._printers[printer.getKey()].connect() printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) @@ -72,4 +70,4 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): elif state_change == ServiceStateChange.Removed: pass # TODO; This isn't testable right now. We need to also decide how to handle - # + # \ No newline at end of file diff --git a/__init__.py b/__init__.py index 76d4094709..d91262f7ba 100644 --- a/__init__.py +++ b/__init__.py @@ -16,5 +16,4 @@ def getMetaData(): } def register(app): - return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin()} - + return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin()} \ No newline at end of file From bd413b9a2b1e4358e0f766c82161c50b0c77a42d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Jun 2016 09:42:53 +0200 Subject: [PATCH 039/297] Added signalemitter CURA-49 --- NetworkPrinterOutputDevice.py | 2 ++ NetworkPrinterOutputDevicePlugin.py | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 189ba9b095..551f575686 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -5,6 +5,7 @@ import requests from UM.i18n import i18nCatalog from UM.Application import Application from UM.Logger import Logger +from UM.Signal import signalemitter from UM.Message import Message @@ -17,6 +18,7 @@ i18n_catalog = i18nCatalog("cura") ## Network connected (wifi / lan) printer that uses the Ultimaker API +@signalemitter class NetworkPrinterOutputDevice(PrinterOutputDevice): def __init__(self, key, address, info): super().__init__(key) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index fe8629f8d6..946e510608 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -2,14 +2,15 @@ from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from . import NetworkPrinterOutputDevice from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange -from UM.Signal import Signal, SignalEmitter +from UM.Signal import Signal, signalemitter from UM.Application import Application ## This plugin handles the connection detection & creation of output device objects for the UM3 printer. # Zero-Conf is used to detect printers, which are saved in a dict. # If we discover a printer that has the same key as the active machine instance a connection is made. -class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): +@signalemitter +class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): def __init__(self): super().__init__() self._zero_conf = Zeroconf() @@ -37,17 +38,16 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin, SignalEmitter): return for key in self._printers: - if key == active_machine.getKey(): + if key == active_machine.getMetaDataEntry("key"): self._printers[key].connect() self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) - else: self._printers[key].close() ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addPrinter(self, name, address, properties): printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) self._printers[printer.getKey()] = printer - if printer.getKey() == Application.getInstance().getGlobalContainerStack().getKey(): + if printer.getKey() == Application.getInstance().getGlobalContainerStack().getMetaDataEntry("key"): self._printers[printer.getKey()].connect() printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) From 486fc7f17643332313689a5f222f93deb069a043 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Jun 2016 09:46:01 +0200 Subject: [PATCH 040/297] When no globalcontainer is set, no check to connect is performed CURA-49 --- NetworkPrinterOutputDevicePlugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 946e510608..9c187e5065 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -47,9 +47,10 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): def addPrinter(self, name, address, properties): printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) self._printers[printer.getKey()] = printer - if printer.getKey() == Application.getInstance().getGlobalContainerStack().getMetaDataEntry("key"): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("key"): self._printers[printer.getKey()].connect() - printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) ## Handler for when the connection state of one of the detected printers changes def _onPrinterConnectionStateChanged(self, key): From 1acf155e19a16afbf4f5d5f7da7ac6e83002820b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Jun 2016 11:49:28 +0200 Subject: [PATCH 041/297] We don't depend on requests lib anymore Intead we use qt network stuff for everything. CURA-49 --- NetworkPrinterOutputDevice.py | 126 +++++++++++++++++----------------- 1 file changed, 64 insertions(+), 62 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 551f575686..b3ae5bb40b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -1,7 +1,3 @@ -import threading -import time -import requests - from UM.i18n import i18nCatalog from UM.Application import Application from UM.Logger import Logger @@ -12,7 +8,9 @@ from UM.Message import Message from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager -from PyQt5.QtCore import QUrl +from PyQt5.QtCore import QUrl, QTimer + +import json i18n_catalog = i18nCatalog("cura") @@ -27,7 +25,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._info = info self._gcode = None - self._update_thread = None # This holds the full JSON file that was received from the last request. self._json_printer_state = None @@ -49,50 +46,40 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._manager.finished.connect(self._onFinished) ## Hack to ensure that the qt networking stuff isn't garbage collected (unless we want it to) - self._qt_request = None - self._qt_reply = None - self._qt_multi_part = None - self._qt_part = None + self._printer_request = None + self._printer_reply = None + + self._print_job_request = None + self._print_job_reply = None + + self._post_request = None + self._post_reply = None + self._post_multi_part = None + self._post_part = None self._progress_message = None self._error_message = None + self._update_timer = QTimer() + self._update_timer.setInterval(5000) # TODO; Add preference for update interval + self._update_timer.setSingleShot(False) + self._update_timer.timeout.connect(self._update) + ## Get the unique key of this machine # \return key String containing the key of the machine. def getKey(self): return self._key def _update(self): - Logger.log("d", "Update thread of printer with key %s and ip %s started", self._key, self._address) - while self.isConnected(): - try: - printer_reply = self._httpGet("printer") - if printer_reply.status_code == 200: - self._json_printer_state = printer_reply.json() - try: - self._spliceJSONData() - except: - # issues with json parsing should not break by definition - # TODO: Check in what cases it should fail. - pass - if self._connection_state == ConnectionState.connecting: - # First successful response, so we are now "connected" - self.setConnectionState(ConnectionState.connected) - else: - Logger.log("w", "Got http status code %s while trying to request data.", printer_reply.status_code) - self.setConnectionState(ConnectionState.error) + ## Request 'general' printer data + url = QUrl("http://" + self._address + self._api_prefix + "printer") + self._printer_request = QNetworkRequest(url) + self._printer_reply = self._manager.get(self._printer_request) - print_job_reply = self._httpGet("print_job") - if print_job_reply.status_code != 404: - self.setProgress(print_job_reply.json()["progress"]) - else: - self.setProgress(0) - - except Exception as e: - self.setConnectionState(ConnectionState.error) - Logger.log("w", "Exception occured while connecting; %s", str(e)) - time.sleep(2) # Poll every 2 seconds for printer state. - Logger.log("d", "Update thread of printer with key %s and ip %s stopped with state: %s", self._key, self._address, self._connection_state) + ## Request print_job data + url = QUrl("http://" + self._address + self._api_prefix + "print_job") + self._print_job_request = QNetworkRequest(url) + self._print_job_reply = self._manager.get(self._print_job_request) ## Convenience function that gets information from the received json data and converts it to the right internal # values / variables @@ -112,9 +99,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def close(self): self._connection_state == ConnectionState.closed - if self._update_thread is not None: - self._update_thread.join() - self._update_thread = None + self._update_timer.stop() def requestWrite(self, node, file_name = None, filter_by_machine = False): self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") @@ -123,13 +108,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def isConnected(self): return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error - ## Start the polling thread. + ## Start requesting data from printer def connect(self): - if self._update_thread is None: - self.setConnectionState(ConnectionState.connecting) - self._update_thread = threading.Thread(target = self._update) - self._update_thread.daemon = True - self._update_thread.start() + self.setConnectionState(ConnectionState.connecting) + self._update() # Manually trigger the first update, as we don't want to wait a few secs before it starts. + Logger.log("d", "Connection with printer %s with ip %s started", self._key, self._address) + self._update_timer.start() def getCameraImage(self): pass # TODO: This still needs to be implemented (we don't have a place to show it now anyway) @@ -143,6 +127,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) self._progress_message.show() + ## Mash the data into single string single_string_file_data = "" for line in self._gcode: single_string_file_data += line @@ -151,23 +136,23 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): file_name = "test.gcode" ## Create multi_part request - self._qt_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) + self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) ## Create part (to be placed inside multipart) - self._qt_part = QHttpPart() - self._qt_part.setHeader(QNetworkRequest.ContentDispositionHeader, + self._post_part = QHttpPart() + self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name) - self._qt_part.setBody(single_string_file_data) - self._qt_multi_part.append(self._qt_part) + self._post_part.setBody(single_string_file_data.encode()) + self._post_multi_part.append(self._post_part) url = QUrl("http://" + self._address + self._api_prefix + "print_job") ## Create the QT request - self._qt_request = QNetworkRequest(url) + self._post_request = QNetworkRequest(url) ## Post request + data - self._qt_reply = self._manager.post(self._qt_request, self._qt_multi_part) - self._qt_reply.uploadProgress.connect(self._onUploadProgress) + self._post_reply = self._manager.post(self._post_request, self._post_multi_part) + self._post_reply.uploadProgress.connect(self._onUploadProgress) except IOError: self._progress_message.hide() @@ -177,15 +162,32 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message.hide() Logger.log("e" , "An exception occured in network connection: %s" % str(e)) + ## Handler for all requests that have finshed. def _onFinished(self, reply): - reply.uploadProgress.disconnect(self._onUploadProgress) - self._progress_message.hide() + if reply.operation() == QNetworkAccessManager.GetOperation: + if "printer" in reply.url().toString(): # Status update from printer. + if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: + if self._connection_state == ConnectionState.connecting: + self.setConnectionState(ConnectionState.connected) + self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8")) + + self._spliceJSONData() + else: + pass # TODO: Handle errors + elif "print_job" in reply.url().toString(): # Status update from print_job: + if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: + self.setProgress(json.loads(bytes(reply.readAll()).decode("utf-8"))["progress"]) + elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: + self.setProgress(0) # No print job found, so there can't be progress! + + elif reply.operation() == QNetworkAccessManager.PostOperation: + reply.uploadProgress.disconnect(self._onUploadProgress) + self._progress_message.hide() + else: + print("got unhandled operation:", reply.operation()) def _onUploadProgress(self, bytes_sent, bytes_total): if bytes_total > 0: self._progress_message.setProgress(bytes_sent / bytes_total * 100) else: - self._progress_message.setProgress(0) - - def _httpGet(self, path): - return requests.get("http://" + self._address + self._api_prefix + path, timeout = 5) \ No newline at end of file + self._progress_message.setProgress(0) \ No newline at end of file From 1220d32ca56c5452ce6a8adb8e4da4c9fd1b486a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Jun 2016 13:23:05 +0200 Subject: [PATCH 042/297] No longer possible to send print_jobs when print just started CURA-49 --- NetworkPrinterOutputDevice.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index b3ae5bb40b..6fddf2d284 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -160,9 +160,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._error_message.show() except Exception as e: self._progress_message.hide() - Logger.log("e" , "An exception occured in network connection: %s" % str(e)) + Logger.log("e", "An exception occurred in network connection: %s" % str(e)) - ## Handler for all requests that have finshed. + ## Handler for all requests that have finished. def _onFinished(self, reply): if reply.operation() == QNetworkAccessManager.GetOperation: if "printer" in reply.url().toString(): # Status update from printer. @@ -176,7 +176,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass # TODO: Handle errors elif "print_job" in reply.url().toString(): # Status update from print_job: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: - self.setProgress(json.loads(bytes(reply.readAll()).decode("utf-8"))["progress"]) + progress = json.loads(bytes(reply.readAll()).decode("utf-8"))["progress"] + ## If progress is 0 add a bit so another print can't be sent. + if progress == 0: + progress += 0.1 + self.setProgress(progress) elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: self.setProgress(0) # No print job found, so there can't be progress! @@ -184,7 +188,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() else: - print("got unhandled operation:", reply.operation()) + Logger.log("d", "NetworkPrinterOutputDevice got an unhandled operation %s", reply.operation()) def _onUploadProgress(self, bytes_sent, bytes_total): if bytes_total > 0: From cadf5d85b79c8a85b81aff11bb762069835436c1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Jun 2016 13:46:04 +0200 Subject: [PATCH 043/297] Camera image is now also retrieved CURA-338 --- NetworkPrinterOutputDevice.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 6fddf2d284..c897df8a60 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -9,6 +9,7 @@ from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager from PyQt5.QtCore import QUrl, QTimer +from PyQt5.QtGui import QPixmap import json @@ -52,6 +53,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._print_job_request = None self._print_job_reply = None + self._image_request = None + self._image_reply = None + self._post_request = None self._post_reply = None self._post_multi_part = None @@ -65,6 +69,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) + self._camera_image = QPixmap() + ## Get the unique key of this machine # \return key String containing the key of the machine. def getKey(self): @@ -81,6 +87,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._print_job_request = QNetworkRequest(url) self._print_job_reply = self._manager.get(self._print_job_request) + ## Request new image + url = QUrl("http://" + self._address +":8080/?action=snapshot") + self._image_request = QNetworkRequest(url) + self._image_reply = self._manager.get(self._image_request) + ## Convenience function that gets information from the received json data and converts it to the right internal # values / variables def _spliceJSONData(self): @@ -116,7 +127,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.start() def getCameraImage(self): - pass # TODO: This still needs to be implemented (we don't have a place to show it now anyway) + return self._camera_image def startPrint(self): if self._progress != 0: @@ -183,7 +194,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setProgress(progress) elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: self.setProgress(0) # No print job found, so there can't be progress! - + elif "snapshot" in reply.url().toString(): # Status update from image: + if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: + self._camera_image.loadFromData(reply.readAll()) elif reply.operation() == QNetworkAccessManager.PostOperation: reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() From ac4dbe2304194bcfa4250443030f85ffd6fa20ad Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Jun 2016 16:01:06 +0200 Subject: [PATCH 044/297] Decreased the progress added when 0 was returned Turned out that by adding 0.1, it started at 10% --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index c897df8a60..712c48a865 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -190,7 +190,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): progress = json.loads(bytes(reply.readAll()).decode("utf-8"))["progress"] ## If progress is 0 add a bit so another print can't be sent. if progress == 0: - progress += 0.1 + progress += 0.001 self.setProgress(progress) elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: self.setProgress(0) # No print job found, so there can't be progress! From 7d258485c8223a7e35b2d1ae6cf8f5150432a007 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 15 Jun 2016 14:17:59 +0200 Subject: [PATCH 045/297] Small fixes so switching printers correctly connects / disconnects output devices CURA-1036 --- NetworkPrinterOutputDevice.py | 2 +- NetworkPrinterOutputDevicePlugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 712c48a865..efc34ecf52 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -109,7 +109,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._updateHeadPosition(head_x, head_y, head_z) def close(self): - self._connection_state == ConnectionState.closed + self.setConnectionState(ConnectionState.closed) self._update_timer.stop() def requestWrite(self, node, file_name = None, filter_by_machine = False): diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 9c187e5065..fed761b772 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -32,7 +32,6 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._zero_conf.close() def _onGlobalStackChanged(self): - active_machine = Application.getInstance().getGlobalContainerStack() if not active_machine: return @@ -41,6 +40,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if key == active_machine.getMetaDataEntry("key"): self._printers[key].connect() self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + else: self._printers[key].close() ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. From 61565561ffe9341d497cb71971fb53919f64c3cb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 15 Jun 2016 15:05:28 +0200 Subject: [PATCH 046/297] Temperatures for both extruders are now correctly retrieved CURA-1036 --- NetworkPrinterOutputDevice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index efc34ecf52..33e3da64de 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -34,6 +34,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## It's okay to leave this for now, as this plugin is um3 only (and has 2 extruders by definition) self._num_extruders = 2 + self._hotend_temperatures = [0] * self._num_extruders + self._target_hotend_temperatures = [0] * self._num_extruders + self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" self.setName(key) @@ -96,7 +99,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # values / variables def _spliceJSONData(self): # Check for hotend temperatures - for index in range(0, self._num_extruders - 1): + for index in range(0, self._num_extruders): temperature = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["temperature"]["current"] self._setHotendTemperature(index, temperature) From 99a30573ce8bb5e734b8409201435a8a3f0d8dd4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 15 Jun 2016 16:28:05 +0200 Subject: [PATCH 047/297] Store current temp in bed_temperature instead of dict with target & current CURA-1036 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 33e3da64de..57a7086b97 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -103,7 +103,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): temperature = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["temperature"]["current"] self._setHotendTemperature(index, temperature) - bed_temperature = self._json_printer_state["bed"]["temperature"] + bed_temperature = self._json_printer_state["bed"]["temperature"]["current"] self._setBedTemperature(bed_temperature) head_x = self._json_printer_state["heads"][0]["position"]["x"] From df943736645c579df0919ae3f388246cd393e749 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 15 Jun 2016 16:46:41 +0200 Subject: [PATCH 048/297] TimeTotal and timeSpent are now set CURA-1068 --- NetworkPrinterOutputDevice.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 57a7086b97..f943e97bc3 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -190,11 +190,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass # TODO: Handle errors elif "print_job" in reply.url().toString(): # Status update from print_job: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: - progress = json.loads(bytes(reply.readAll()).decode("utf-8"))["progress"] + json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) + progress = json_data["progress"] ## If progress is 0 add a bit so another print can't be sent. if progress == 0: progress += 0.001 self.setProgress(progress) + + self.setTimeElapsed(json_data["time_elapsed"]) + self.setTimeTotal(json_data["time_total"]) + elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: self.setProgress(0) # No print job found, so there can't be progress! elif "snapshot" in reply.url().toString(): # Status update from image: From 354d2bc10984a5da67c9332146120bcfb1eca284 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Jun 2016 09:36:19 +0200 Subject: [PATCH 049/297] Camera image now works with a image provider CURA-1036 and CURA-338 --- NetworkPrinterOutputDevice.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f943e97bc3..c384d0f710 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -8,8 +8,8 @@ from UM.Message import Message from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager -from PyQt5.QtCore import QUrl, QTimer -from PyQt5.QtGui import QPixmap +from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty +from PyQt5.QtGui import QPixmap, QImage import json @@ -72,7 +72,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) - self._camera_image = QPixmap() + self._camera_image_id = 0 + + self._camera_image = QImage() ## Get the unique key of this machine # \return key String containing the key of the machine. @@ -129,6 +131,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("d", "Connection with printer %s with ip %s started", self._key, self._address) self._update_timer.start() + newImage = pyqtSignal() + + @pyqtProperty(QUrl, notify = newImage) + def cameraImage(self): + self._camera_image_id += 1 + temp = "image://camera/" + str(self._camera_image_id) + return QUrl(temp, QUrl.TolerantMode) + def getCameraImage(self): return self._camera_image @@ -205,6 +215,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif "snapshot" in reply.url().toString(): # Status update from image: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: self._camera_image.loadFromData(reply.readAll()) + self.newImage.emit() elif reply.operation() == QNetworkAccessManager.PostOperation: reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() From bb1fead4c66345f83c7635c2320cb5aebf96a9f3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Jun 2016 09:40:48 +0200 Subject: [PATCH 050/297] Moved camera to own timer, so grabbing can be in a different frequency CURA-336 and CURA-1036 --- NetworkPrinterOutputDevice.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index c384d0f710..f979db2f6d 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -72,6 +72,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) + self._camera_timer = QTimer() + self._camera_timer.setInterval(2000) # Todo: Add preference for camera update interval + self._camera_timer.setSingleShot(False) + self._camera_timer.timeout.connect(self._update_camera) + self._camera_image_id = 0 self._camera_image = QImage() @@ -81,6 +86,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def getKey(self): return self._key + def _update_camera(self): + ## Request new image + url = QUrl("http://" + self._address + ":8080/?action=snapshot") + self._image_request = QNetworkRequest(url) + self._image_reply = self._manager.get(self._image_request) + def _update(self): ## Request 'general' printer data url = QUrl("http://" + self._address + self._api_prefix + "printer") @@ -92,11 +103,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._print_job_request = QNetworkRequest(url) self._print_job_reply = self._manager.get(self._print_job_request) - ## Request new image - url = QUrl("http://" + self._address +":8080/?action=snapshot") - self._image_request = QNetworkRequest(url) - self._image_reply = self._manager.get(self._image_request) - ## Convenience function that gets information from the received json data and converts it to the right internal # values / variables def _spliceJSONData(self): @@ -116,6 +122,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def close(self): self.setConnectionState(ConnectionState.closed) self._update_timer.stop() + self._camera_timer.stop() def requestWrite(self, node, file_name = None, filter_by_machine = False): self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") @@ -128,8 +135,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def connect(self): self.setConnectionState(ConnectionState.connecting) self._update() # Manually trigger the first update, as we don't want to wait a few secs before it starts. + self._update_camera() Logger.log("d", "Connection with printer %s with ip %s started", self._key, self._address) self._update_timer.start() + self._camera_timer.start() newImage = pyqtSignal() From c6b88118dcd467d697e0271df6752163b51c7d16 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Jun 2016 11:43:53 +0200 Subject: [PATCH 051/297] Jobstate is now also tracked --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f979db2f6d..2a9a71b3ac 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -215,7 +215,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if progress == 0: progress += 0.001 self.setProgress(progress) - + self.setJobState(json_data["state"]) self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) From 9dbdd7fe7a311d513f76c4206efe03f9cec03f5d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Jun 2016 15:35:40 +0200 Subject: [PATCH 052/297] Added changing printjob state This also adds the bits required to actually do a put request CURA-1036 --- NetworkPrinterOutputDevice.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2a9a71b3ac..cae59fd287 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -64,6 +64,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._post_multi_part = None self._post_part = None + self._put_request = None + self._put_reply = None + self._progress_message = None self._error_message = None @@ -151,6 +154,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def getCameraImage(self): return self._camera_image + def _setJobState(self, job_state): + url = QUrl("http://" + self._address + self._api_prefix + "print_job/state") + self._put_request = QNetworkRequest(url) + self._put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + data = "{\"target\": \"%s\"}" % job_state + self._put_reply = self._manager.put(self._put_request, data.encode()) + def startPrint(self): if self._progress != 0: self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) @@ -215,7 +225,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if progress == 0: progress += 0.001 self.setProgress(progress) - self.setJobState(json_data["state"]) + self._updateJobState(json_data["state"]) self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) @@ -228,6 +238,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif reply.operation() == QNetworkAccessManager.PostOperation: reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() + elif reply.operation() == QNetworkAccessManager.PutOperation: + if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 204: + pass # Request was sucesfull! + else: + Logger.log("d","Something went wrong when trying to update data of API. %s", reply.readAll()) else: Logger.log("d", "NetworkPrinterOutputDevice got an unhandled operation %s", reply.operation()) From a36157c538d619bad39ed07dfc0650fd35feeadb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Jun 2016 11:18:10 +0200 Subject: [PATCH 053/297] Jobstate is reset when there is no print job CURA-1036 --- NetworkPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index cae59fd287..bcc8283771 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -231,6 +231,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: self.setProgress(0) # No print job found, so there can't be progress! + self._updateJobState("") elif "snapshot" in reply.url().toString(): # Status update from image: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: self._camera_image.loadFromData(reply.readAll()) From 44c2a091182ed8397fcff4ccee748e5aa5d3459c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Jun 2016 11:34:28 +0200 Subject: [PATCH 054/297] Added stubs for checking if a print is at all possible CURA-1036 --- NetworkPrinterOutputDevice.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index bcc8283771..fc6a75dde0 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -129,6 +129,19 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def requestWrite(self, node, file_name = None, filter_by_machine = False): self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") + + # TODO: Implement all checks. + # Check if cartridges are loaded at all (Error) + #self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"] != "" + + # Check if there is material loaded at all (Error) + #self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"] != "" + + # Check if there is enough material (Warning) + #self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["length_remaining"] + + #TODO: Check if the cartridge is the right ID (give warning otherwise) + self.startPrint() def isConnected(self): From 0b9af6055cdf0fcf6f9c0fb9c0e364c3524f1cf9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Jun 2016 11:34:54 +0200 Subject: [PATCH 055/297] Decreased sample time for print information CURA-1036 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index fc6a75dde0..7802767647 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -71,7 +71,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._error_message = None self._update_timer = QTimer() - self._update_timer.setInterval(5000) # TODO; Add preference for update interval + self._update_timer.setInterval(2000) # TODO; Add preference for update interval self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) From be779cb92aa8e31dfff8d1db30f3c3a5b6ce386c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Jun 2016 13:00:05 +0200 Subject: [PATCH 056/297] Improved logging when put operation failed CURA-1036 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 7802767647..e9c8c483bd 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -256,7 +256,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 204: pass # Request was sucesfull! else: - Logger.log("d","Something went wrong when trying to update data of API. %s", reply.readAll()) + Logger.log("d","Something went wrong when trying to update data of API. %s statuscode: %s", reply.readAll(), reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) else: Logger.log("d", "NetworkPrinterOutputDevice got an unhandled operation %s", reply.operation()) From f8cac563705f383ccd033b99711f6d7eb0186c01 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 17 Jun 2016 15:46:18 +0200 Subject: [PATCH 057/297] Added DiscoverUM3 machine action stub CURA-1385 --- DiscoverUM3Action.py | 5 +++++ __init__.py | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 DiscoverUM3Action.py diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py new file mode 100644 index 0000000000..c5c853cca8 --- /dev/null +++ b/DiscoverUM3Action.py @@ -0,0 +1,5 @@ +from cura.MachineAction import MachineAction + +class DiscoverUM3Action(MachineAction): + def __init__(self): + super().__init__("DiscoverUM3Action") \ No newline at end of file diff --git a/__init__.py b/__init__.py index d91262f7ba..1efc63e8d0 100644 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,7 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from . import NetworkPrinterOutputDevicePlugin - +from . import DiscoverUM3Action from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -12,8 +12,11 @@ def getMetaData(): "author": "Ultimaker", "description": catalog.i18nc("Wifi connection", "Wifi connection"), "api": 3 - } + }, + "profile_reader": [ + {} + ] } def register(app): - return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin()} \ No newline at end of file + return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()} \ No newline at end of file From bca613b47e7b44e04d61eacca2d0e45a09636f11 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 20 Jun 2016 13:43:43 +0200 Subject: [PATCH 058/297] Added label CURA-1385 --- DiscoverUM3Action.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index c5c853cca8..848e1c26f2 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -2,4 +2,7 @@ from cura.MachineAction import MachineAction class DiscoverUM3Action(MachineAction): def __init__(self): - super().__init__("DiscoverUM3Action") \ No newline at end of file + super().__init__("DiscoverUM3Action", "Discover printers") + + def _execute(self): + pass \ No newline at end of file From d7bb807e2b0ccb927932fe999e1913378b4df3d8 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 13:35:25 +0200 Subject: [PATCH 059/297] Added stub QML page for discovery action CURA-336 --- DiscoverUM3Action.py | 1 + DiscoverUM3Action.qml | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 DiscoverUM3Action.qml diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 848e1c26f2..89b024b8bd 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -3,6 +3,7 @@ from cura.MachineAction import MachineAction class DiscoverUM3Action(MachineAction): def __init__(self): super().__init__("DiscoverUM3Action", "Discover printers") + self._qml_url = "DiscoverUM3Action.qml" def _execute(self): pass \ No newline at end of file diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml new file mode 100644 index 0000000000..1d26d143f8 --- /dev/null +++ b/DiscoverUM3Action.qml @@ -0,0 +1,26 @@ +import UM 1.2 as UM +import Cura 1.0 as Cura + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +Cura.MachineAction +{ + anchors.fill: parent; + Item + { + anchors.fill: parent; + id: discoverUM3Action + UM.I18nCatalog { id: catalog; name:"cura"} + Label + { + id: pageTitle + width: parent.width + text: catalog.i18nc("@title", "Discover Printer") + wrapMode: Text.WordWrap + font.pointSize: 18; + } + } +} \ No newline at end of file From f199d490bb7de893fc68d2064faad2f853680361 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 14:04:50 +0200 Subject: [PATCH 060/297] Action now has a list of found UM3 keys CURA-336 --- DiscoverUM3Action.py | 27 +++++++++++++++++++++++++-- DiscoverUM3Action.qml | 5 +++++ NetworkPrinterOutputDevicePlugin.py | 3 +++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 89b024b8bd..48cd3de247 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -1,9 +1,32 @@ from cura.MachineAction import MachineAction +from UM.Application import Application + +from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot + class DiscoverUM3Action(MachineAction): def __init__(self): super().__init__("DiscoverUM3Action", "Discover printers") self._qml_url = "DiscoverUM3Action.qml" - def _execute(self): - pass \ No newline at end of file + self._network_plugin = None + + printerDetected = pyqtSignal() + + @pyqtSlot() + def startDiscovery(self): + if not self._network_plugin: + self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("JediWifiPrintingPlugin") + self._network_plugin.addPrinterSignal.connect(self._onPrinterAdded) + self.printerDetected.emit() + + def _onPrinterAdded(self, *args): + self.printerDetected.emit() + + @pyqtProperty("QVariantList", notify = printerDetected) + def foundDevices(self): + if self._network_plugin: + print(list(self._network_plugin.getPrinters().keys())) + return list(self._network_plugin.getPrinters().keys()) + else: + return [] diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 1d26d143f8..7935298008 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -22,5 +22,10 @@ Cura.MachineAction wrapMode: Text.WordWrap font.pointSize: 18; } + Button + { + text: "Start looking!" + onClicked: manager.startDiscovery() + } } } \ No newline at end of file diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index fed761b772..cd4de6e4f8 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -31,6 +31,9 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): def stop(self): self._zero_conf.close() + def getPrinters(self): + return self._printers + def _onGlobalStackChanged(self): active_machine = Application.getInstance().getGlobalContainerStack() if not active_machine: From 9a4fc0345c6a55fab6bccbd807ec2c72585863bb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 14:17:50 +0200 Subject: [PATCH 061/297] All found devices are now listed in the discover action CURA-336 --- DiscoverUM3Action.py | 2 +- DiscoverUM3Action.qml | 23 ++++++++++++++++++++++- NetworkPrinterOutputDevicePlugin.py | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 48cd3de247..be71a05e28 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -26,7 +26,7 @@ class DiscoverUM3Action(MachineAction): @pyqtProperty("QVariantList", notify = printerDetected) def foundDevices(self): if self._network_plugin: - print(list(self._network_plugin.getPrinters().keys())) + return list(self._network_plugin.getPrinters().keys()) else: return [] diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 7935298008..b9883cb47c 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -9,7 +9,7 @@ import QtQuick.Window 2.1 Cura.MachineAction { anchors.fill: parent; - Item + Column { anchors.fill: parent; id: discoverUM3Action @@ -27,5 +27,26 @@ Cura.MachineAction text: "Start looking!" onClicked: manager.startDiscovery() } + + ListView + { + model: manager.foundDevices + width: parent.width + height: 500 + delegate: Rectangle + { + height: childrenRect.height; + color: "white" + width: parent.width + Label + { + anchors.left: parent.left; + anchors.leftMargin: UM.Theme.getSize("default_margin").width; + anchors.right: parent.right; + text: modelData + elide: Text.ElideRight + } + } + } } } \ No newline at end of file diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index cd4de6e4f8..6697e05bd9 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -67,7 +67,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if state_change == ServiceStateChange.Added: info = zeroconf.get_service_info(service_type, name) if info: - if info.properties.get(b"type", None): + if info.properties.get(b"type", None) == b'printer': address = '.'.join(map(lambda n: str(n), info.address)) self.addPrinterSignal.emit(str(name), address, info.properties) From 16fcf2208b1edb11d5f6d6fcfff2cec6f1024bd1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 14:19:56 +0200 Subject: [PATCH 062/297] Refactoring & exposing of properties CURA-336 --- NetworkPrinterOutputDevice.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e9c8c483bd..02646bbba2 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -19,11 +19,11 @@ i18n_catalog = i18nCatalog("cura") ## Network connected (wifi / lan) printer that uses the Ultimaker API @signalemitter class NetworkPrinterOutputDevice(PrinterOutputDevice): - def __init__(self, key, address, info): + def __init__(self, key, address, properties): super().__init__(key) self._address = address self._key = key - self._info = info + self._properties = properties # Properties dict as provided by zero conf self._gcode = None @@ -41,7 +41,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._api_prefix = "/api/v" + self._api_version + "/" self.setName(key) self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print with WIFI")) - self.setDescription(i18n_catalog.i18nc("@info:tooltip", "Print with WIFI")) + self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print with WIFI")) self.setIconName("print") # QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly @@ -84,6 +84,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._camera_image = QImage() + def getProperties(self): + return self._properties + ## Get the unique key of this machine # \return key String containing the key of the machine. def getKey(self): From 0cee5b78df96f44d7dae7b632fc003bd8ce17782 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 14:25:46 +0200 Subject: [PATCH 063/297] Print discovery now lists all found human readable names CURA-336 --- DiscoverUM3Action.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index be71a05e28..68da635e11 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -26,7 +26,7 @@ class DiscoverUM3Action(MachineAction): @pyqtProperty("QVariantList", notify = printerDetected) def foundDevices(self): if self._network_plugin: - - return list(self._network_plugin.getPrinters().keys()) + printers = self._network_plugin.getPrinters() + return([printers[printer].getProperties().get(b"name").decode("utf-8") for printer in printers]) else: return [] From e94f94767e95df0a9de528287107e77177914250 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 14:39:50 +0200 Subject: [PATCH 064/297] Updated UX of detected printers CURA-336 --- DiscoverUM3Action.py | 2 +- DiscoverUM3Action.qml | 70 +++++++++++++++++++++++++++++++++---------- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 68da635e11..c8dd2eda41 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -27,6 +27,6 @@ class DiscoverUM3Action(MachineAction): def foundDevices(self): if self._network_plugin: printers = self._network_plugin.getPrinters() - return([printers[printer].getProperties().get(b"name").decode("utf-8") for printer in printers]) + return [printers[printer].getProperties().get(b"name").decode("utf-8") for printer in printers] else: return [] diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index b9883cb47c..100de777c7 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -13,38 +13,78 @@ Cura.MachineAction { anchors.fill: parent; id: discoverUM3Action + SystemPalette { id: palette } UM.I18nCatalog { id: catalog; name:"cura"} Label { id: pageTitle width: parent.width - text: catalog.i18nc("@title", "Discover Printer") + text: catalog.i18nc("@title", "Connect to Networked Printer") wrapMode: Text.WordWrap font.pointSize: 18; } + + Label + { + id: pageDescription + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your ptiner is connected to the network using a network cable of by connecting your printer to your WIFI network. \n\n If you don't want to connect Cura with your Ultimaker 3 now, you can always use a USB drive to transfer g-code files to your Printer.\n\n Select your Ultimaker 3 from the list below:") + } Button { text: "Start looking!" onClicked: manager.startDiscovery() } - - ListView + Row { - model: manager.foundDevices width: parent.width - height: 500 - delegate: Rectangle + ScrollView { - height: childrenRect.height; - color: "white" - width: parent.width - Label + id: objectListContainer + frameVisible: true; + width: parent.width * 0.5 + + Rectangle { - anchors.left: parent.left; - anchors.leftMargin: UM.Theme.getSize("default_margin").width; - anchors.right: parent.right; - text: modelData - elide: Text.ElideRight + parent: viewport + anchors.fill: parent + color: palette.light + } + + ListView + { + model: manager.foundDevices + width: parent.width + height: 500 + currentIndex: activeIndex + delegate: Rectangle + { + height: childrenRect.height; + color: ListView.isCurrentItem ? palette.highlight : index % 2 ? palette.base : palette.alternateBase + width: parent.width + Label + { + anchors.left: parent.left; + anchors.leftMargin: UM.Theme.getSize("default_margin").width; + anchors.right: parent.right; + text: modelData + elide: Text.ElideRight + } + + MouseArea + { + anchors.fill: parent; + onClicked: + { + if(!parent.ListView.isCurrentItem) + { + parent.ListView.view.currentIndex = index; + //base.itemActivated(); + } + } + } + } } } } From 01eb65f30360e5d8ebb1acb34da9db2b2bddb971 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 15:02:45 +0200 Subject: [PATCH 065/297] Removed accidental profile_reader stuff --- __init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/__init__.py b/__init__.py index 1efc63e8d0..711b7caf93 100644 --- a/__init__.py +++ b/__init__.py @@ -12,10 +12,7 @@ def getMetaData(): "author": "Ultimaker", "description": catalog.i18nc("Wifi connection", "Wifi connection"), "api": 3 - }, - "profile_reader": [ - {} - ] + } } def register(app): From 3b51c31772cdfc7c8c0b34a8064edef5af938f5e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 15:46:12 +0200 Subject: [PATCH 066/297] Meta data of the printer is now also visible CURA-336 --- DiscoverUM3Action.py | 2 +- DiscoverUM3Action.qml | 60 +++++++++++++++++++++++++++++++++-- NetworkPrinterOutputDevice.py | 15 +++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index c8dd2eda41..aade48e806 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -27,6 +27,6 @@ class DiscoverUM3Action(MachineAction): def foundDevices(self): if self._network_plugin: printers = self._network_plugin.getPrinters() - return [printers[printer].getProperties().get(b"name").decode("utf-8") for printer in printers] + return [printers[printer] for printer in printers] else: return [] diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 100de777c7..fcb529536e 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -8,7 +8,9 @@ import QtQuick.Window 2.1 Cura.MachineAction { + id: base anchors.fill: parent; + property var selectedPrinter: null Column { anchors.fill: parent; @@ -39,6 +41,7 @@ Cura.MachineAction Row { width: parent.width + spacing: UM.Theme.getSize("default_margin").width ScrollView { id: objectListContainer @@ -54,10 +57,12 @@ Cura.MachineAction ListView { + id: listview model: manager.foundDevices width: parent.width height: 500 currentIndex: activeIndex + onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex] delegate: Rectangle { height: childrenRect.height; @@ -68,7 +73,7 @@ Cura.MachineAction anchors.left: parent.left; anchors.leftMargin: UM.Theme.getSize("default_margin").width; anchors.right: parent.right; - text: modelData + text: listview.model[index].name elide: Text.ElideRight } @@ -80,13 +85,64 @@ Cura.MachineAction if(!parent.ListView.isCurrentItem) { parent.ListView.view.currentIndex = index; - //base.itemActivated(); } } } } } } + Column + { + width: parent.width * 0.5 + Label + { + width: parent.width + wrapMode: Text.WordWrap + text: base.selectedPrinter ? base.selectedPrinter.name : "" + font.pointSize: 16; + } + Grid + { + width: parent.width + columns: 2 + Label + { + width: parent.width * 0.5 + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Type") + } + Label + { + width: parent.width * 0.5 + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Ultimaker 3") + } + Label + { + width: parent.width * 0.5 + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "Firmware version") + } + Label + { + width: parent.width * 0.5 + wrapMode: Text.WordWrap + text: base.selectedPrinter ? base.selectedPrinter.firmwareVersion : "" + } + Label + { + width: parent.width * 0.5 + wrapMode: Text.WordWrap + text: catalog.i18nc("@label", "IP Address") + } + Label + { + width: parent.width * 0.5 + wrapMode: Text.WordWrap + text: base.selectedPrinter ? base.selectedPrinter.ipAddress : "" + } + } + } } } } \ No newline at end of file diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 02646bbba2..d243d62dcc 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -92,6 +92,21 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def getKey(self): return self._key + ## Name of the printer (as returned from the zeroConf properties) + @pyqtProperty(str, constant = True) + def name(self): + return self._properties.get(b"name", b"").decode("utf-8") + + ## Firmware version (as returned from the zeroConf properties) + @pyqtProperty(str, constant=True) + def firmwareVersion(self): + return self._properties.get(b"firmware_version", b"").decode("utf-8") + + ## IPadress of this printer + @pyqtProperty(str, constant=True) + def ipAddress(self): + return self._address + def _update_camera(self): ## Request new image url = QUrl("http://" + self._address + ":8080/?action=snapshot") From bb9d3a47ef90a241ed963dd580347c1a63a66818 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 16:15:56 +0200 Subject: [PATCH 067/297] Printer can now actually be linked CURA-336 --- DiscoverUM3Action.py | 9 +++++++++ DiscoverUM3Action.qml | 22 ++++++++++++++++++++++ NetworkPrinterOutputDevice.py | 5 +++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index aade48e806..55afcd35fa 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -30,3 +30,12 @@ class DiscoverUM3Action(MachineAction): return [printers[printer] for printer in printers] else: return [] + + @pyqtSlot(str) + def setKey(self, key): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + if "key" in global_container_stack.getMetaData(): + global_container_stack.setMetaDataEntry("key", key) + else: + global_container_stack.addMetaDataEntry("key", key) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index fcb529536e..704dcc7851 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -145,4 +145,26 @@ Cura.MachineAction } } } + Button + { + text: catalog.i18nc("@action:button", "Ok") + anchors.right: cancelButton.left + anchors.bottom: parent.bottom + onClicked: + { + manager.setKey(base.selectedPrinter.getKey()) + completed() + } + } + Button + { + id: cancelButton + text: catalog.i18nc("@action:button", "Cancel") + anchors.right: discoverUM3Action.right + anchors.bottom: parent.bottom + onClicked: + { + completed() + } + } } \ No newline at end of file diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index d243d62dcc..2d5b2f1b19 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -8,8 +8,8 @@ from UM.Message import Message from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager -from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty -from PyQt5.QtGui import QPixmap, QImage +from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot +from PyQt5.QtGui import QImage import json @@ -89,6 +89,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Get the unique key of this machine # \return key String containing the key of the machine. + @pyqtSlot(result = str) def getKey(self): return self._key From a61ac82433a653962ece793c50366de7a5491b95 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 16:20:20 +0200 Subject: [PATCH 068/297] Linking a key to a printer now re-checks connection CURA-336 --- DiscoverUM3Action.py | 4 ++++ NetworkPrinterOutputDevicePlugin.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 55afcd35fa..b492b79d8b 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -39,3 +39,7 @@ class DiscoverUM3Action(MachineAction): global_container_stack.setMetaDataEntry("key", key) else: global_container_stack.addMetaDataEntry("key", key) + + if self._network_plugin: + # Ensure that the connection states are refreshed. + self._network_plugin.reCheckConnections() diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 6697e05bd9..ad41d984ae 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -19,7 +19,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addPrinterSignal.connect(self.addPrinter) - Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) + Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) addPrinterSignal = Signal() @@ -34,7 +34,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): def getPrinters(self): return self._printers - def _onGlobalStackChanged(self): + def reCheckConnections(self): active_machine = Application.getInstance().getGlobalContainerStack() if not active_machine: return From a1c9f055c868629865674a8c1d8e808d3e0592cc Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 16:24:17 +0200 Subject: [PATCH 069/297] UI updates CURA-336 --- DiscoverUM3Action.qml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 704dcc7851..fdb986394e 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -33,11 +33,7 @@ Cura.MachineAction wrapMode: Text.WordWrap text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your ptiner is connected to the network using a network cable of by connecting your printer to your WIFI network. \n\n If you don't want to connect Cura with your Ultimaker 3 now, you can always use a USB drive to transfer g-code files to your Printer.\n\n Select your Ultimaker 3 from the list below:") } - Button - { - text: "Start looking!" - onClicked: manager.startDiscovery() - } + Row { width: parent.width @@ -94,6 +90,7 @@ Cura.MachineAction Column { width: parent.width * 0.5 + visible: base.selectedPrinter Label { width: parent.width @@ -144,6 +141,11 @@ Cura.MachineAction } } } + Button + { + text: catalog.i18nc("@label","Start looking!") + onClicked: manager.startDiscovery() + } } Button { From d6aacf24b95583f3600d3534025377f9822a3a1f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jun 2016 17:07:20 +0200 Subject: [PATCH 070/297] Added JobName to network output device CURA-1036 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2d5b2f1b19..8ddf318be6 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -260,7 +260,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._updateJobState(json_data["state"]) self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) - + self.setJobName(json_data["name"]) elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: self.setProgress(0) # No print job found, so there can't be progress! self._updateJobState("") From 07c25edb0fad63500bfe401b49270c5cc77feb91 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 24 Jun 2016 13:47:52 +0200 Subject: [PATCH 071/297] Removed unused code --- HttpUploadDataStream.py | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 HttpUploadDataStream.py diff --git a/HttpUploadDataStream.py b/HttpUploadDataStream.py deleted file mode 100644 index a3e5a82ba4..0000000000 --- a/HttpUploadDataStream.py +++ /dev/null @@ -1,32 +0,0 @@ -from UM.Signal import Signal, SignalEmitter -class HttpUploadDataStream(SignalEmitter): - def __init__(self): - super().__init__() - self._data_list = [] - self._total_length = 0 - self._read_position = 0 - - progressSignal = Signal() - - def write(self, data): - data = bytes(data,'UTF-8') - size = len(data) - if size < 1: - return - blocks = int(size / 2048) - for n in range(0, blocks): - self._data_list.append(data[n*2048:n*2048+2048]) - self._data_list.append(data[blocks*2048:]) - self._total_length += size - - def read(self, size): - if self._read_position >= len(self._data_list): - return None - ret = self._data_list[self._read_position] - self._read_position += 1 - - self.progressSignal.emit(float(self._read_position) / float(len(self._data_list))) - return ret - - def __len__(self): - return self._total_length \ No newline at end of file From 260694f2334e1e5cc5fa102cb61583f0b0d85ed9 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 27 Jun 2016 09:58:40 +0200 Subject: [PATCH 072/297] Code style, typos and minute style tweak CURA-336 --- DiscoverUM3Action.qml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index fdb986394e..cf0262abf0 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -31,7 +31,7 @@ Cura.MachineAction id: pageDescription width: parent.width wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your ptiner is connected to the network using a network cable of by connecting your printer to your WIFI network. \n\n If you don't want to connect Cura with your Ultimaker 3 now, you can always use a USB drive to transfer g-code files to your Printer.\n\n Select your Ultimaker 3 from the list below:") + text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. \n\nIf you don't want to connect Cura with your Ultimaker 3 now, you can always use a USB drive to transfer g-code files to your printer.\n\nSelect your Ultimaker 3 from the list below:") } Row @@ -61,15 +61,16 @@ Cura.MachineAction onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex] delegate: Rectangle { - height: childrenRect.height; + height: childrenRect.height color: ListView.isCurrentItem ? palette.highlight : index % 2 ? palette.base : palette.alternateBase width: parent.width Label { - anchors.left: parent.left; - anchors.leftMargin: UM.Theme.getSize("default_margin").width; - anchors.right: parent.right; + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.right: parent.right text: listview.model[index].name + color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text elide: Text.ElideRight } From 0860fc6cf57e529b259056058705249bafe4d834 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 27 Jun 2016 10:32:56 +0200 Subject: [PATCH 073/297] Automatically start discovery CURA-336 --- DiscoverUM3Action.qml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index cf0262abf0..9b1046b445 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -59,6 +59,7 @@ Cura.MachineAction height: 500 currentIndex: activeIndex onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex] + Component.onCompleted: manager.startDiscovery() delegate: Rectangle { height: childrenRect.height @@ -142,11 +143,6 @@ Cura.MachineAction } } } - Button - { - text: catalog.i18nc("@label","Start looking!") - onClicked: manager.startDiscovery() - } } Button { From 7af7748fff323449f5b48758c462c206757efeb8 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 27 Jun 2016 14:40:07 +0200 Subject: [PATCH 074/297] Make machine action button translatable CURA-336 --- DiscoverUM3Action.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index b492b79d8b..2ee14b4b9d 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -4,9 +4,12 @@ from UM.Application import Application from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + class DiscoverUM3Action(MachineAction): def __init__(self): - super().__init__("DiscoverUM3Action", "Discover printers") + super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network")) self._qml_url = "DiscoverUM3Action.qml" self._network_plugin = None From 8deb0dc2743d1d85899cb636b88ed831c05838a9 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 27 Jun 2016 14:40:07 +0200 Subject: [PATCH 075/297] Make machine action button translatable CURA-336 --- DiscoverUM3Action.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index b492b79d8b..2ee14b4b9d 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -4,9 +4,12 @@ from UM.Application import Application from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + class DiscoverUM3Action(MachineAction): def __init__(self): - super().__init__("DiscoverUM3Action", "Discover printers") + super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network")) self._qml_url = "DiscoverUM3Action.qml" self._network_plugin = None From 53c1c738d539b85256e670d80443cfcb1a82c74f Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 27 Jun 2016 16:51:29 +0200 Subject: [PATCH 076/297] Fix error CURA-49 --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 8ddf318be6..72dff3e275 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -257,13 +257,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if progress == 0: progress += 0.001 self.setProgress(progress) - self._updateJobState(json_data["state"]) + self._setJobState(json_data["state"]) self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) self.setJobName(json_data["name"]) elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: self.setProgress(0) # No print job found, so there can't be progress! - self._updateJobState("") + self._setJobState("") elif "snapshot" in reply.url().toString(): # Status update from image: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: self._camera_image.loadFromData(reply.readAll()) From b8fddf6527d4096d8b43784a86c2bb99ab1115f5 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 27 Jun 2016 17:49:47 +0200 Subject: [PATCH 077/297] Tweak codestyle and styling CURA-336 --- DiscoverUM3Action.qml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 9b1046b445..d74a39ade7 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -16,14 +16,14 @@ Cura.MachineAction anchors.fill: parent; id: discoverUM3Action SystemPalette { id: palette } - UM.I18nCatalog { id: catalog; name:"cura"} + UM.I18nCatalog { id: catalog; name:"cura" } Label { id: pageTitle width: parent.width text: catalog.i18nc("@title", "Connect to Networked Printer") wrapMode: Text.WordWrap - font.pointSize: 18; + font.pointSize: 18 } Label @@ -41,8 +41,9 @@ Cura.MachineAction ScrollView { id: objectListContainer - frameVisible: true; + frameVisible: true width: parent.width * 0.5 + height: base.height - parent.y Rectangle { @@ -56,7 +57,6 @@ Cura.MachineAction id: listview model: manager.foundDevices width: parent.width - height: 500 currentIndex: activeIndex onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex] Component.onCompleted: manager.startDiscovery() @@ -98,7 +98,8 @@ Cura.MachineAction width: parent.width wrapMode: Text.WordWrap text: base.selectedPrinter ? base.selectedPrinter.name : "" - font.pointSize: 16; + font: UM.Theme.getFont("large") + elide: Text.ElideRight } Grid { From 76dfb5e718449c7d5e960a0e7ef14aa1b34f2f5a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 11:48:54 +0200 Subject: [PATCH 078/297] Renamed generic key to um_network_key CURA-49 --- DiscoverUM3Action.py | 4 ++-- NetworkPrinterOutputDevicePlugin.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index b492b79d8b..4415b5f20a 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -36,9 +36,9 @@ class DiscoverUM3Action(MachineAction): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: if "key" in global_container_stack.getMetaData(): - global_container_stack.setMetaDataEntry("key", key) + global_container_stack.setMetaDataEntry("um_network_key", key) else: - global_container_stack.addMetaDataEntry("key", key) + global_container_stack.addMetaDataEntry("um_network_key", key) if self._network_plugin: # Ensure that the connection states are refreshed. diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index ad41d984ae..cee9e5654c 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -40,7 +40,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): return for key in self._printers: - if key == active_machine.getMetaDataEntry("key"): + if key == active_machine.getMetaDataEntry("um_network_key"): self._printers[key].connect() self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) else: @@ -51,7 +51,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) self._printers[printer.getKey()] = printer global_container_stack = Application.getInstance().getGlobalContainerStack() - if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("key"): + if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"): self._printers[printer.getKey()].connect() printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) From 2711a9bc95bf1cb73697dd8f76b970632776f617 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 11:52:55 +0200 Subject: [PATCH 079/297] Changed name of plugin to better describe what it's doing CURA-49 --- __init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index 711b7caf93..b030fe61e6 100644 --- a/__init__.py +++ b/__init__.py @@ -8,9 +8,9 @@ catalog = i18nCatalog("cura") def getMetaData(): return { "plugin": { - "name": "Wifi connection", + "name": "UM3 Network Connection", "author": "Ultimaker", - "description": catalog.i18nc("Wifi connection", "Wifi connection"), + "description": catalog.i18nc("Wifi connection", "UM3 Network Connection"), "api": 3 } } From 5e51d4d998dcb709a4dc0011e76d5b6a9ee2cef9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 12:59:37 +0200 Subject: [PATCH 080/297] Minor refactor to make code more readable CURA-336 --- DiscoverUM3Action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 51b5253ea2..8fed85d3e1 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -30,7 +30,7 @@ class DiscoverUM3Action(MachineAction): def foundDevices(self): if self._network_plugin: printers = self._network_plugin.getPrinters() - return [printers[printer] for printer in printers] + return list(printers.values()) else: return [] From ab934d356b073c972755555348b8474e8b7426ef Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 13:46:20 +0200 Subject: [PATCH 081/297] Expanded logging --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 72dff3e275..e3844efb2d 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -275,7 +275,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 204: pass # Request was sucesfull! else: - Logger.log("d","Something went wrong when trying to update data of API. %s statuscode: %s", reply.readAll(), reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) + Logger.log("d", "Something went wrong when trying to update data of API (%s). Message: %s Statuscode: %s", reply.url().toString(), reply.readAll(), reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) else: Logger.log("d", "NetworkPrinterOutputDevice got an unhandled operation %s", reply.operation()) From e53ff78704fcd1b5b3ccfa8133b50400bd309683 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 13:51:19 +0200 Subject: [PATCH 082/297] Revert "Fix error" This reverts commit 53c1c738d539b85256e670d80443cfcb1a82c74f. The code was intentional. The fix actually caused issues, as it sent data it got from the server back again --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e3844efb2d..3e3b9791c5 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -257,13 +257,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if progress == 0: progress += 0.001 self.setProgress(progress) - self._setJobState(json_data["state"]) + self._updateJobState(json_data["state"]) self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) self.setJobName(json_data["name"]) elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: self.setProgress(0) # No print job found, so there can't be progress! - self._setJobState("") + self._updateJobState("") elif "snapshot" in reply.url().toString(): # Status update from image: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: self._camera_image.loadFromData(reply.readAll()) From 4c7d235a0d6ecd8dd21edda72d69da3c6289a69c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 14:50:55 +0200 Subject: [PATCH 083/297] Fixed mistake with updating linked key --- DiscoverUM3Action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 8fed85d3e1..27c5ef6638 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -38,7 +38,7 @@ class DiscoverUM3Action(MachineAction): def setKey(self, key): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: - if "key" in global_container_stack.getMetaData(): + if "um_network_key" in global_container_stack.getMetaData(): global_container_stack.setMetaDataEntry("um_network_key", key) else: global_container_stack.addMetaDataEntry("um_network_key", key) From bc50f0fa7cdb5ef04cefb7790ac4a249ad5f69b4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 15:00:35 +0200 Subject: [PATCH 084/297] Only close connection if it's open when re-checking connections --- NetworkPrinterOutputDevicePlugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index cee9e5654c..4055b59a1a 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -44,7 +44,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._printers[key].connect() self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) else: - self._printers[key].close() + if self._printers[key].isConnected(): + self._printers[key].close() ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addPrinter(self, name, address, properties): From 15e11e1cadae97927c7341ba6f83dee6f3c42440 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 16:37:37 +0200 Subject: [PATCH 085/297] Added basic authentication for UM3 Note that the authentication is not saved at the moment, so this step needs to be repeated on every boot CURA-49 --- NetworkPrinterOutputDevice.py | 47 +++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 3e3b9791c5..b0bd37c64a 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -48,6 +48,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # hook itself into the event loop, which results in events never being fired / done. self._manager = QNetworkAccessManager() self._manager.finished.connect(self._onFinished) + self._manager.authenticationRequired.connect(self._onAuthenticationRequired) ## Hack to ensure that the qt networking stuff isn't garbage collected (unless we want it to) self._printer_request = None @@ -82,8 +83,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._camera_image_id = 0 + self._authenticated = False + self._authentication_id = None + self._authentication_key = None + self._camera_image = QImage() + def _onAuthenticationRequired(self, reply, authenticator): + if self._authentication_id is not None and self._authentication_key is not None: + authenticator.setUser(self._authentication_id) + authenticator.setPassword(self._authentication_key) + def getProperties(self): return self._properties @@ -115,6 +125,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._image_reply = self._manager.get(self._image_request) def _update(self): + if not self._authenticated: + self._checkAuthentication() ## Request 'general' printer data url = QUrl("http://" + self._address + self._api_prefix + "printer") self._printer_request = QNetworkRequest(url) @@ -237,6 +249,19 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message.hide() Logger.log("e", "An exception occurred in network connection: %s" % str(e)) + ## Verify if we are authenticated to make requests. + def _checkAuthentication(self): + url = QUrl("http://" + self._address + self._api_prefix + "auth/verify") + request = QNetworkRequest(url) + self._manager.get(request) + + ## Request a authentication key from the printer so we can be authenticated + def _requestAuthentication(self): + url = QUrl("http://" + self._address + self._api_prefix + "auth/request") + request = QNetworkRequest(url) + request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + self._manager.post(request, json.dumps({"application": "Cura", "user":"test"}).encode()) + ## Handler for all requests that have finished. def _onFinished(self, reply): if reply.operation() == QNetworkAccessManager.GetOperation: @@ -268,9 +293,27 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: self._camera_image.loadFromData(reply.readAll()) self.newImage.emit() + elif "auth/verify" in reply.url().toString(): # Answer when requesting authentication + if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 401: + Logger.log("i", "Not authenticated. Attempting to request authentication") + self._requestAuthentication() + else: + self._authenticated = True + Logger.log("i", "Authentication succeeded") + elif reply.operation() == QNetworkAccessManager.PostOperation: - reply.uploadProgress.disconnect(self._onUploadProgress) - self._progress_message.hide() + if "/auth/request" in reply.url().toString(): + # We got a response to requesting authentication. + data = json.loads(bytes(reply.readAll()).decode("utf-8")) + self._authentication_key = data["key"] + self._authentication_id = data["id"] + Logger.log("i", "Got a new authentication ID. Waiting for authorization: %s", self._authentication_id ) + + # Continue with handshaking; send request to printer so it can be authenticated. + self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id)))) + else: + reply.uploadProgress.disconnect(self._onUploadProgress) + self._progress_message.hide() elif reply.operation() == QNetworkAccessManager.PutOperation: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 204: pass # Request was sucesfull! From 6244d74c1d0a15150ea81cf2ea4dabcd5b5db518 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 16:45:02 +0200 Subject: [PATCH 086/297] Code cleanup --- NetworkPrinterOutputDevice.py | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index b0bd37c64a..414861b38b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -50,24 +50,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._manager.finished.connect(self._onFinished) self._manager.authenticationRequired.connect(self._onAuthenticationRequired) - ## Hack to ensure that the qt networking stuff isn't garbage collected (unless we want it to) - self._printer_request = None - self._printer_reply = None - - self._print_job_request = None - self._print_job_reply = None - - self._image_request = None - self._image_reply = None - self._post_request = None self._post_reply = None self._post_multi_part = None self._post_part = None - self._put_request = None - self._put_reply = None - self._progress_message = None self._error_message = None @@ -121,21 +108,21 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _update_camera(self): ## Request new image url = QUrl("http://" + self._address + ":8080/?action=snapshot") - self._image_request = QNetworkRequest(url) - self._image_reply = self._manager.get(self._image_request) + image_request = QNetworkRequest(url) + self._manager.get(image_request) def _update(self): if not self._authenticated: self._checkAuthentication() ## Request 'general' printer data url = QUrl("http://" + self._address + self._api_prefix + "printer") - self._printer_request = QNetworkRequest(url) - self._printer_reply = self._manager.get(self._printer_request) + printer_request = QNetworkRequest(url) + self._manager.get(printer_request) ## Request print_job data url = QUrl("http://" + self._address + self._api_prefix + "print_job") - self._print_job_request = QNetworkRequest(url) - self._print_job_reply = self._manager.get(self._print_job_request) + print_job_request = QNetworkRequest(url) + self._manager.get(print_job_request) ## Convenience function that gets information from the received json data and converts it to the right internal # values / variables @@ -200,10 +187,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _setJobState(self, job_state): url = QUrl("http://" + self._address + self._api_prefix + "print_job/state") - self._put_request = QNetworkRequest(url) - self._put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + put_request = QNetworkRequest(url) + put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") data = "{\"target\": \"%s\"}" % job_state - self._put_reply = self._manager.put(self._put_request, data.encode()) + self._manager.put(put_request, data.encode()) def startPrint(self): if self._progress != 0: From c13a6966d0f93b0f37267e6d8c1efa2d7f06e7f3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 28 Jun 2016 17:01:33 +0200 Subject: [PATCH 087/297] Handle authentication denied messages CURA-49 --- NetworkPrinterOutputDevice.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 414861b38b..632c4c6d43 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -15,6 +15,13 @@ import json i18n_catalog = i18nCatalog("cura") +from enum import IntEnum + +class AuthState(IntEnum): + NotAuthenticated = 1 + AuthenticationRequested = 2 + Authenticated = 3 + AuthenticationDenied = 4 ## Network connected (wifi / lan) printer that uses the Ultimaker API @signalemitter @@ -70,7 +77,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._camera_image_id = 0 - self._authenticated = False + self._authentication_state = AuthState.NotAuthenticated self._authentication_id = None self._authentication_key = None @@ -112,7 +119,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._manager.get(image_request) def _update(self): - if not self._authenticated: + if self._authentication_state in [AuthState.NotAuthenticated, AuthState.AuthenticationRequested]: self._checkAuthentication() ## Request 'general' printer data url = QUrl("http://" + self._address + self._api_prefix + "printer") @@ -247,6 +254,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): url = QUrl("http://" + self._address + self._api_prefix + "auth/request") request = QNetworkRequest(url) request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + self._authentication_state = AuthState.AuthenticationRequested self._manager.post(request, json.dumps({"application": "Cura", "user":"test"}).encode()) ## Handler for all requests that have finished. @@ -285,8 +293,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("i", "Not authenticated. Attempting to request authentication") self._requestAuthentication() else: - self._authenticated = True + self._authenticated = AuthState.Authenticated Logger.log("i", "Authentication succeeded") + elif "auth/check" in reply.url().toString(): # Check if we are authenticated (user can refuse this!) + data = json.loads(bytes(reply.readAll()).decode("utf-8")) + if data.get("message", "") == "authorized": + Logger.log("i", "Authentication completed.") + self._authentication_state = AuthState.Authenticated + else: + Logger.log("i", "Authentication was denied.") + self._authentication_state = AuthState.AuthenticationDenied elif reply.operation() == QNetworkAccessManager.PostOperation: if "/auth/request" in reply.url().toString(): From c8ba60fad4bb161016fd7a44bb85238b1aae00dc Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 10:18:02 +0200 Subject: [PATCH 088/297] Authentication message is now shown when authenticating CURA-49 --- NetworkPrinterOutputDevice.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 632c4c6d43..02334a3c4b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -81,6 +81,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_id = None self._authentication_key = None + self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Requested access. Please aprove the request on the printer"), lifetime = 0, dismissable = False) self._camera_image = QImage() def _onAuthenticationRequired(self, reply, authenticator): @@ -118,6 +119,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): image_request = QNetworkRequest(url) self._manager.get(image_request) + def setAuthenticationState(self, auth_state): + if auth_state == AuthState.AuthenticationRequested: + self._authentication_requested_message.show() + elif auth_state == AuthState.Authenticated: + self._authentication_requested_message.hide() + elif auth_state == AuthState.AuthenticationDenied: + self._authentication_requested_message.hide() + + self._authentication_state = auth_state + def _update(self): if self._authentication_state in [AuthState.NotAuthenticated, AuthState.AuthenticationRequested]: self._checkAuthentication() @@ -254,7 +265,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): url = QUrl("http://" + self._address + self._api_prefix + "auth/request") request = QNetworkRequest(url) request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") - self._authentication_state = AuthState.AuthenticationRequested + self.setAuthenticationState(AuthState.AuthenticationRequested) self._manager.post(request, json.dumps({"application": "Cura", "user":"test"}).encode()) ## Handler for all requests that have finished. @@ -290,19 +301,25 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.newImage.emit() elif "auth/verify" in reply.url().toString(): # Answer when requesting authentication if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 401: - Logger.log("i", "Not authenticated. Attempting to request authentication") - self._requestAuthentication() + if self._authentication_state != AuthState.AuthenticationRequested: + # Only request a new authentication when we have not already done so. + Logger.log("i", "Not authenticated. Attempting to request authentication") + self._requestAuthentication() + elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 403: + print("403") else: - self._authenticated = AuthState.Authenticated + self.setAuthenticationState(AuthState.Authenticated) Logger.log("i", "Authentication succeeded") elif "auth/check" in reply.url().toString(): # Check if we are authenticated (user can refuse this!) data = json.loads(bytes(reply.readAll()).decode("utf-8")) + print("auth check response") if data.get("message", "") == "authorized": Logger.log("i", "Authentication completed.") - self._authentication_state = AuthState.Authenticated + self.setAuthenticationState(AuthState.Authenticated) else: - Logger.log("i", "Authentication was denied.") - self._authentication_state = AuthState.AuthenticationDenied + pass + #Logger.log("i", "Authentication was denied.") + #self.setAuthenticationState(AuthState.AuthenticationDenied) elif reply.operation() == QNetworkAccessManager.PostOperation: if "/auth/request" in reply.url().toString(): From 3a767c72f07cf59bafe178dc7259df3a93167ecb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 10:55:45 +0200 Subject: [PATCH 089/297] Authentication can now timeout and be denied from printer side CURA-49 --- NetworkPrinterOutputDevice.py | 40 ++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 02334a3c4b..9987fcda36 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -77,13 +77,28 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._camera_image_id = 0 + self._authentication_counter = 0 + self._max_authentication_counter = 30 # Number of attempts before authentication timed out. + + self._authentication_timer = QTimer() + self._authentication_timer.setInterval(1000) # TODO; Add preference for update interval + self._authentication_timer.setSingleShot(False) + self._authentication_timer.timeout.connect(self._onAuthenticationTimer) + self._authentication_state = AuthState.NotAuthenticated self._authentication_id = None self._authentication_key = None - self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Requested access. Please aprove the request on the printer"), lifetime = 0, dismissable = False) + self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Requested access. Please aprove the request on the printer"), lifetime = 0, dismissable = False, progress = 0) self._camera_image = QImage() + def _onAuthenticationTimer(self): + self._authentication_counter += 1 + self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100) + if self._authentication_counter > self._max_authentication_counter: + self._authentication_timer.stop() + self.setAuthenticationState(AuthState.AuthenticationDenied) + def _onAuthenticationRequired(self, reply, authenticator): if self._authentication_id is not None and self._authentication_key is not None: authenticator.setUser(self._authentication_id) @@ -122,6 +137,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def setAuthenticationState(self, auth_state): if auth_state == AuthState.AuthenticationRequested: self._authentication_requested_message.show() + self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: self._authentication_requested_message.hide() elif auth_state == AuthState.AuthenticationDenied: @@ -130,8 +146,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_state = auth_state def _update(self): - if self._authentication_state in [AuthState.NotAuthenticated, AuthState.AuthenticationRequested]: - self._checkAuthentication() + if self._authentication_state == AuthState.NotAuthenticated: + self._verifyAuthentication() # We don't know if we are authenticated; check if we have correct auth. + elif self._authentication_state == AuthState.AuthenticationRequested: + self._checkAuthentication() # We requested authentication at some point. Check if we got permission. ## Request 'general' printer data url = QUrl("http://" + self._address + self._api_prefix + "printer") printer_request = QNetworkRequest(url) @@ -170,7 +188,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Check if cartridges are loaded at all (Error) #self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"] != "" - # Check if there is material loaded at all (Error) + # Check if there is material loaded at all (Error)self.authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter) #self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"] != "" # Check if there is enough material (Warning) @@ -255,11 +273,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("e", "An exception occurred in network connection: %s" % str(e)) ## Verify if we are authenticated to make requests. - def _checkAuthentication(self): + def _verifyAuthentication(self): url = QUrl("http://" + self._address + self._api_prefix + "auth/verify") request = QNetworkRequest(url) self._manager.get(request) + def _checkAuthentication(self): + self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id)))) + ## Request a authentication key from the printer so we can be authenticated def _requestAuthentication(self): url = QUrl("http://" + self._address + self._api_prefix + "auth/request") @@ -306,7 +327,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("i", "Not authenticated. Attempting to request authentication") self._requestAuthentication() elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 403: - print("403") + pass else: self.setAuthenticationState(AuthState.Authenticated) Logger.log("i", "Authentication succeeded") @@ -316,6 +337,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if data.get("message", "") == "authorized": Logger.log("i", "Authentication completed.") self.setAuthenticationState(AuthState.Authenticated) + elif data.get("message", "") == "unauthorized": + Logger.log("i", "Authentication was denied.") + self.setAuthenticationState(AuthState.AuthenticationDenied) else: pass #Logger.log("i", "Authentication was denied.") @@ -329,8 +353,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_id = data["id"] Logger.log("i", "Got a new authentication ID. Waiting for authorization: %s", self._authentication_id ) - # Continue with handshaking; send request to printer so it can be authenticated. - self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id)))) + # Check if the authentication is accepted. + self._checkAuthentication() else: reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() From cba372737333e70139fcdbb7e4e9ea23d1ff7c56 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 29 Jun 2016 11:24:25 +0200 Subject: [PATCH 090/297] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e0573ec934..8aea59b4ff 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # JediWifiPrintingPlugin Secret plugin to enable wifi printing from Cura to JediPrinter + + +Simply copy this into [Cura installation folder]/plugins/JediWifiPrintingPlugin From a90be88a0420bc14e7d69f539c1b020c66870d2e Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 29 Jun 2016 11:34:02 +0200 Subject: [PATCH 091/297] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8aea59b4ff..40a21e8abb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # JediWifiPrintingPlugin Secret plugin to enable wifi printing from Cura to JediPrinter - -Simply copy this into [Cura installation folder]/plugins/JediWifiPrintingPlugin +Intructions +---- +- Clone repo into [Cura installation folder]/plugins/JediWifiPrintingPlugin (Or somewhere else and add a link..) +- sudo apt-get install python3-zeroconf +- sudo apt-get install python-zeroconf # is this one needed? From c3ecacf6c091a4e5daeb25519bf89affe80dbd30 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 29 Jun 2016 11:40:32 +0200 Subject: [PATCH 092/297] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 40a21e8abb..33aa76c306 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,3 @@ Intructions ---- - Clone repo into [Cura installation folder]/plugins/JediWifiPrintingPlugin (Or somewhere else and add a link..) - sudo apt-get install python3-zeroconf -- sudo apt-get install python-zeroconf # is this one needed? From 054f7aaa8315c25de9d0617000654c965bf880a5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 11:53:56 +0200 Subject: [PATCH 093/297] If authentication is recieved, it's now also correctly validated CURA-49 --- NetworkPrinterOutputDevice.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 9987fcda36..15f33aedde 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -335,15 +335,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): data = json.loads(bytes(reply.readAll()).decode("utf-8")) print("auth check response") if data.get("message", "") == "authorized": - Logger.log("i", "Authentication completed.") - self.setAuthenticationState(AuthState.Authenticated) + Logger.log("i", "Authentication was approved") + self._verifyAuthentication() # Ensure that the verification is really used and correct. elif data.get("message", "") == "unauthorized": Logger.log("i", "Authentication was denied.") self.setAuthenticationState(AuthState.AuthenticationDenied) else: pass - #Logger.log("i", "Authentication was denied.") - #self.setAuthenticationState(AuthState.AuthenticationDenied) elif reply.operation() == QNetworkAccessManager.PostOperation: if "/auth/request" in reply.url().toString(): From 4b6993bf7bdf635ecdfa9795b1ac63b85650bba0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 12:00:28 +0200 Subject: [PATCH 094/297] Message is now shown when trying to print without authentication CURA-49 --- NetworkPrinterOutputDevice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 15f33aedde..5f6f0f9378 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -233,6 +233,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) self._error_message.show() return + elif self._authentication_state != AuthState.Authenticated: + self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", + "Not authenticated to print with this machine. Unable to start a new job.")) + self._not_authenticated_message.show() + return try: self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) self._progress_message.show() @@ -333,7 +338,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("i", "Authentication succeeded") elif "auth/check" in reply.url().toString(): # Check if we are authenticated (user can refuse this!) data = json.loads(bytes(reply.readAll()).decode("utf-8")) - print("auth check response") if data.get("message", "") == "authorized": Logger.log("i", "Authentication was approved") self._verifyAuthentication() # Ensure that the verification is really used and correct. From dd92ac7c5f626da4fe83237397f1f2be7ab25270 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 12:06:36 +0200 Subject: [PATCH 095/297] Username & cura version are now sent in the auth request CURA-49 --- NetworkPrinterOutputDevice.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 5f6f0f9378..f7e7c0777b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -12,6 +12,7 @@ from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot from PyQt5.QtGui import QImage import json +import os i18n_catalog = i18nCatalog("cura") @@ -228,6 +229,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): data = "{\"target\": \"%s\"}" % job_state self._manager.put(put_request, data.encode()) + def _getUserName(self): + for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'): + user = os.environ.get(name) + if user: + return user + return "Unknown User" # Couldn't find out username. + def startPrint(self): if self._progress != 0: self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) @@ -292,7 +300,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): request = QNetworkRequest(url) request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") self.setAuthenticationState(AuthState.AuthenticationRequested) - self._manager.post(request, json.dumps({"application": "Cura", "user":"test"}).encode()) + self._manager.post(request, json.dumps({"application": "Cura-" + Application.getInstance().getVersion(), "user": self._getUserName()}).encode()) ## Handler for all requests that have finished. def _onFinished(self, reply): From acd3274ca44b8fdc8999f06174c97e72d0fe36c9 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 29 Jun 2016 12:07:25 +0200 Subject: [PATCH 096/297] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33aa76c306..277111412c 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,4 @@ Secret plugin to enable wifi printing from Cura to JediPrinter Intructions ---- - Clone repo into [Cura installation folder]/plugins/JediWifiPrintingPlugin (Or somewhere else and add a link..) -- sudo apt-get install python3-zeroconf +- pip3 install python3-zeroconf From 368d4e9ea2af93469a6a066d7ad2aee0e389567b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 12:12:12 +0200 Subject: [PATCH 097/297] Updated documentation CURA-49 --- NetworkPrinterOutputDevice.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f7e7c0777b..2dca836fb6 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -135,6 +135,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): image_request = QNetworkRequest(url) self._manager.get(image_request) + ## Set the authentication state. + # \param auth_state \type{AuthState} Enum value representing the new auth state def setAuthenticationState(self, auth_state): if auth_state == AuthState.AuthenticationRequested: self._authentication_requested_message.show() @@ -146,6 +148,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_state = auth_state + ## Request data from the connected device. def _update(self): if self._authentication_state == AuthState.NotAuthenticated: self._verifyAuthentication() # We don't know if we are authenticated; check if we have correct auth. @@ -229,6 +232,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): data = "{\"target\": \"%s\"}" % job_state self._manager.put(put_request, data.encode()) + ## Convenience function to get the username from the OS. + # The code was copied from the getpass module, as we try to use as little dependencies as possible. def _getUserName(self): for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'): user = os.environ.get(name) @@ -236,6 +241,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return user return "Unknown User" # Couldn't find out username. + ## Attempt to start a new print. + # This function can fail to actually start a print due to not being authenticated or another print already + # being in progress. def startPrint(self): if self._progress != 0: self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) @@ -291,6 +299,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): request = QNetworkRequest(url) self._manager.get(request) + ## Check if the authentication request was allowed by the printer. def _checkAuthentication(self): self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id)))) From 5597f6dcf2bc3c9cc6d1c89d62dd20f945546cc4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 13:19:05 +0200 Subject: [PATCH 098/297] Authentication is now saved with machine-instance CURA-49 --- NetworkPrinterOutputDevice.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2dca836fb6..9bb77c46e1 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -211,6 +211,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update() # Manually trigger the first update, as we don't want to wait a few secs before it starts. self._update_camera() Logger.log("d", "Connection with printer %s with ip %s started", self._key, self._address) + + ## Check if this machine was authenticated before. + self._authentication_id = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_id", None) + self._authentication_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_key", None) + self._update_timer.start() self._camera_timer.start() @@ -352,6 +357,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass else: self.setAuthenticationState(AuthState.Authenticated) + global_container_stack = Application.getInstance().getGlobalContainerStack() + ## Save authentication details. + if global_container_stack: + if "network_authentication_key" in global_container_stack.getMetaData(): + global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key) + else: + global_container_stack.addMetaDataEntry("network_authentication_key", self._authentication_key) + if "network_authentication_id" in global_container_stack.getMetaData(): + global_container_stack.setMetaDataEntry("network_authentication_id", self._authentication_id) + else: + global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id) Logger.log("i", "Authentication succeeded") elif "auth/check" in reply.url().toString(): # Check if we are authenticated (user can refuse this!) data = json.loads(bytes(reply.readAll()).decode("utf-8")) From 58c3f5dc89a1e364d58c59aaf0d141195b1825c5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 13:31:42 +0200 Subject: [PATCH 099/297] Closing the networkprinteroutput device now hides all it's messages CURA-49 --- NetworkPrinterOutputDevice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 9bb77c46e1..94ede5add3 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -182,6 +182,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def close(self): self.setConnectionState(ConnectionState.closed) + self._progress_message.hide() + self._authentication_requested_message.hide() + self._error_message.hide() + self._authentication_counter = 0 + self._authentication_timer.stop() self._update_timer.stop() self._camera_timer.stop() From 57ea4f0dc8b3d483e3b15a59018930e885ceb6b9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 13:33:31 +0200 Subject: [PATCH 100/297] Updated auth timeout to 5 min --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 94ede5add3..0b8fe607e5 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -79,7 +79,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._camera_image_id = 0 self._authentication_counter = 0 - self._max_authentication_counter = 30 # Number of attempts before authentication timed out. + self._max_authentication_counter = 5 * 60 # Number of attempts before authentication timed out (5 min) self._authentication_timer = QTimer() self._authentication_timer.setInterval(1000) # TODO; Add preference for update interval From 12dcc43baca449c2b62c9f2652451ca6b8ef37dd Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 29 Jun 2016 13:45:29 +0200 Subject: [PATCH 101/297] Added messages upon succesfull & not sucessfull pairing CURA-49 --- NetworkPrinterOutputDevice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 0b8fe607e5..f62fc6df5a 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -143,9 +143,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: self._authentication_requested_message.hide() + authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Printer was successfully paired with Cura")) + authentication_succeeded_message.show() elif auth_state == AuthState.AuthenticationDenied: self._authentication_requested_message.hide() - + authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed. This can be either due to a timeout or the printer refused the request.")) + authentication_failed_message.show() self._authentication_state = auth_state ## Request data from the connected device. From b9f73a8eefb96507cb6b076e32eb33bf0c14f079 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 30 Jun 2016 09:35:57 +0200 Subject: [PATCH 102/297] Hiding messages is now only done when there are messages CURA-49 --- NetworkPrinterOutputDevice.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f62fc6df5a..ebe850e323 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -185,9 +185,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def close(self): self.setConnectionState(ConnectionState.closed) - self._progress_message.hide() + if self._progress_message: + self._progress_message.hide() self._authentication_requested_message.hide() - self._error_message.hide() + if self._error_message: + self._error_message.hide() self._authentication_counter = 0 self._authentication_timer.stop() self._update_timer.stop() From a4117bd3bebbb1edf4be8727c59f181e324ee701 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 30 Jun 2016 09:52:48 +0200 Subject: [PATCH 103/297] Display data is reset upon job completion --- NetworkPrinterOutputDevice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index ebe850e323..0c8805e026 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -351,8 +351,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setTimeTotal(json_data["time_total"]) self.setJobName(json_data["name"]) elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: - self.setProgress(0) # No print job found, so there can't be progress! + self.setProgress(0) # No print job found, so there can't be progress or other data. self._updateJobState("") + self.setTimeElapsed(0) + self.setTimeTotal(0) + self.setJobName("") elif "snapshot" in reply.url().toString(): # Status update from image: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: self._camera_image.loadFromData(reply.readAll()) From 79ee928883c76773fd34bc55cd769cf031d109b1 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 30 Jun 2016 09:54:08 +0200 Subject: [PATCH 104/297] Change progress to percentages CURA-1036 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f62fc6df5a..b616613dce 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -343,7 +343,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## If progress is 0 add a bit so another print can't be sent. if progress == 0: progress += 0.001 - self.setProgress(progress) + self.setProgress(progress * 100) self._updateJobState(json_data["state"]) self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) From ca79e398c88524dd35ec1e2557ff4971c4025d3c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 30 Jun 2016 14:25:54 +0200 Subject: [PATCH 105/297] All known materials are now send upon connection Due to garbage collection issues, the (multi) part requests are now cached. CURA-334 --- NetworkPrinterOutputDevice.py | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 0c8805e026..f82da80d4c 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -5,6 +5,8 @@ from UM.Signal import signalemitter from UM.Message import Message +import UM.Settings + from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager @@ -63,6 +65,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._post_multi_part = None self._post_part = None + + self._material_multi_part = None + self._material_part = None + self._progress_message = None self._error_message = None @@ -93,6 +99,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Requested access. Please aprove the request on the printer"), lifetime = 0, dismissable = False, progress = 0) self._camera_image = QImage() + self._material_post_objects = {} + def _onAuthenticationTimer(self): self._authentication_counter += 1 self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100) @@ -145,6 +153,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_requested_message.hide() authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Printer was successfully paired with Cura")) authentication_succeeded_message.show() + # Once we are authenticated we need to send all material profiles. + # + self.sendMaterialProfiles() elif auth_state == AuthState.AuthenticationDenied: self._authentication_requested_message.hide() authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed. This can be either due to a timeout or the printer refused the request.")) @@ -326,6 +337,32 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setAuthenticationState(AuthState.AuthenticationRequested) self._manager.post(request, json.dumps({"application": "Cura-" + Application.getInstance().getVersion(), "user": self._getUserName()}).encode()) + ## Send all material profiles to the printer. + def sendMaterialProfiles(self): + for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material"): + try: + xml_data = container.serialize() + if xml_data == "": + continue + material_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) + + material_part = QHttpPart() + file_name = "none.xml" + material_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\";filename=\"%s\"" % file_name) + material_part.setBody(xml_data.encode()) + material_multi_part.append(material_part) + url = QUrl("http://" + self._address + self._api_prefix + "materials") + material_post_request = QNetworkRequest(url) + + self._manager.post(material_post_request, material_multi_part) + + # Keep reference to material_part and material_multi_part so the garbage collector won't touch them. + self._material_post_objects[container.getId()] = (material_part, material_multi_part) + except NotImplementedError: + # If the material container is not the most "generic" one it can't be serialized an will raise a + # NotImplementedError. We can simply ignore these. + pass + ## Handler for all requests that have finished. def _onFinished(self, reply): if reply.operation() == QNetworkAccessManager.GetOperation: @@ -403,6 +440,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Check if the authentication is accepted. self._checkAuthentication() + elif "materials" in reply.url().toString(): + # TODO: Remove cached post request items. + pass else: reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() From bfc7a7e3c956d9df7df11aa82f9aaf8cf27d47ea Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 30 Jun 2016 14:56:36 +0200 Subject: [PATCH 106/297] Multipart data objects used for posting new materials are now deleted upon completion CURA-344 --- NetworkPrinterOutputDevice.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f82da80d4c..3fc22ad4d7 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -353,11 +353,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): material_multi_part.append(material_part) url = QUrl("http://" + self._address + self._api_prefix + "materials") material_post_request = QNetworkRequest(url) + reply = self._manager.post(material_post_request, material_multi_part) - self._manager.post(material_post_request, material_multi_part) - - # Keep reference to material_part and material_multi_part so the garbage collector won't touch them. - self._material_post_objects[container.getId()] = (material_part, material_multi_part) + # Keep reference to material_part, material_multi_part and reply so the garbage collector won't touch them. + self._material_post_objects[id(reply)] = (material_part, material_multi_part, reply) except NotImplementedError: # If the material container is not the most "generic" one it can't be serialized an will raise a # NotImplementedError. We can simply ignore these. @@ -441,8 +440,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Check if the authentication is accepted. self._checkAuthentication() elif "materials" in reply.url().toString(): - # TODO: Remove cached post request items. - pass + # Remove cached post request items. + del self._material_post_objects[id(reply)] else: reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() From ea3a039bdd991c3bb39ef7d9e69720e00db42748 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 1 Jul 2016 15:07:17 +0200 Subject: [PATCH 107/297] Automatically show the Print Monitor when starting a print CURA-1036 --- NetworkPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index af8bf174cc..40f12fd61d 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -196,6 +196,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._camera_timer.stop() def requestWrite(self, node, file_name = None, filter_by_machine = False): + Application.getInstance().showPrintMonitor.emit(True) self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") # TODO: Implement all checks. From 051419577e59ade23ee5bcd259d96cf972d8f91b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 4 Jul 2016 11:58:31 +0200 Subject: [PATCH 108/297] Forward hotend and material data to PrinterOutputDevice CURA-491 --- NetworkPrinterOutputDevice.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index dd7bdbe49e..465889973e 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -44,6 +44,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## It's okay to leave this for now, as this plugin is um3 only (and has 2 extruders by definition) self._num_extruders = 2 + # These are reinitialised here (from PrinterOutputDevice) to match the new _num_extruders self._hotend_temperatures = [0] * self._num_extruders self._target_hotend_temperatures = [0] * self._num_extruders @@ -185,6 +186,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): for index in range(0, self._num_extruders): temperature = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["temperature"]["current"] self._setHotendTemperature(index, temperature) + try: + material_id = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"] + except KeyError: + material_id = "" + self._setMaterialId(index, material_id) + try: + hotend_id = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] + except KeyError: + hotend_id = "" + self._setHotendId(index, hotend_id) bed_temperature = self._json_printer_state["bed"]["temperature"]["current"] self._setBedTemperature(bed_temperature) @@ -214,7 +225,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Check if cartridges are loaded at all (Error) #self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"] != "" - # Check if there is material loaded at all (Error)self.authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter) + # Check if there is material loaded at all (Error) #self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"] != "" # Check if there is enough material (Warning) From 5b686f0a5035fcb4cdee30d43e7ead6fe27e4feb Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 4 Jul 2016 14:26:54 +0200 Subject: [PATCH 109/297] Reinitialise material and hotend lists to new number of extruders CURA-491 --- NetworkPrinterOutputDevice.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 465889973e..928332808a 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -48,6 +48,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._hotend_temperatures = [0] * self._num_extruders self._target_hotend_temperatures = [0] * self._num_extruders + self._material_ids = [""] * self._num_extruders + self._hotend_ids = [""] * self._num_extruders + self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" self.setName(key) From fe4ed3dad82a4c1a41b6428d2e0f45e21f97c1be Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 4 Jul 2016 18:18:42 +0200 Subject: [PATCH 110/297] Use jobname instead of "test" placeholder CURA-49 --- NetworkPrinterOutputDevice.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index dd7bdbe49e..0e14c2985a 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -290,8 +290,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): for line in self._gcode: single_string_file_data += line - ## TODO: Use correct file name (we use placeholder now) - file_name = "test.gcode" + file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName ## Create multi_part request self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) From 4fec82fab73c61ad32834dd006373bcde2298796 Mon Sep 17 00:00:00 2001 From: awhiemstra Date: Tue, 5 Jul 2016 10:31:15 +0200 Subject: [PATCH 111/297] Fix CMakeLists to include the discovery action Also remove a no-longer existing file --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 906af9910c..ad48443607 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,8 @@ cmake_minimum_required(VERSION 2.8.12) install(FILES __init__.py - HttpUploadDataStream.py + DiscoverUM3Action.py + DiscoverUM3Action.qml NetworkPrinterOutputDevice.py NetworkPrinterOutputDevicePlugin.py LICENSE From 676182cd54fd409d3c4c20797e15eec8906ac6a4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 15 Jul 2016 10:52:31 +0200 Subject: [PATCH 112/297] Fixed authentication timeout CURA-1916 --- NetworkPrinterOutputDevice.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index d082206a67..25eefadf9f 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -157,13 +157,22 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_requested_message.hide() authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Printer was successfully paired with Cura")) authentication_succeeded_message.show() + + # Stop waiting for a response + self._authentication_timer.stop() + self._authentication_counter = 0 + # Once we are authenticated we need to send all material profiles. - # self.sendMaterialProfiles() elif auth_state == AuthState.AuthenticationDenied: self._authentication_requested_message.hide() authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed. This can be either due to a timeout or the printer refused the request.")) authentication_failed_message.show() + + # Stop waiting for a response + self._authentication_timer.stop() + self._authentication_counter = 0 + self._authentication_state = auth_state ## Request data from the connected device. From f9f83981bb8ac9a055f0466be049d529403db1cf Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 15 Jul 2016 14:06:50 +0200 Subject: [PATCH 113/297] Changed print with wifi to print over network CURA-1855 --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 25eefadf9f..cef661d1a2 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -54,8 +54,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" self.setName(key) - self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print with WIFI")) - self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print with WIFI")) + self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print over network")) + self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network")) self.setIconName("print") # QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly From 6e84d39cae0c2fc6af4b4efba02e3918c754814c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 15 Jul 2016 14:14:22 +0200 Subject: [PATCH 114/297] Improved error handling of authentication --- NetworkPrinterOutputDevice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index cef661d1a2..cb1a7789ca 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -427,7 +427,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._requestAuthentication() elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 403: pass - else: + elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: self.setAuthenticationState(AuthState.Authenticated) global_container_stack = Application.getInstance().getGlobalContainerStack() ## Save authentication details. @@ -441,6 +441,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): else: global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id) Logger.log("i", "Authentication succeeded") + else: # Got a response that we didn't expect, so something went wrong. + Logger.log("w", "While trying to authenticate, we got an unexpected response: %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) + self.setAuthenticationState(AuthState.NotAuthenticated) + elif "auth/check" in reply.url().toString(): # Check if we are authenticated (user can refuse this!) data = json.loads(bytes(reply.readAll()).decode("utf-8")) if data.get("message", "") == "authorized": From 53a9f5f622546ba23392e6b3ecc25e83f15aaedd Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 15 Jul 2016 14:15:30 +0200 Subject: [PATCH 115/297] Added log message to authentication timeout --- NetworkPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index cb1a7789ca..27a11916d4 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -110,6 +110,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100) if self._authentication_counter > self._max_authentication_counter: self._authentication_timer.stop() + Logger.log("i", "Authentication timer ended. Setting authentication to denied") self.setAuthenticationState(AuthState.AuthenticationDenied) def _onAuthenticationRequired(self, reply, authenticator): From 097d4c9e6b3028fa7f3c12f965ebb7f77da1ed73 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 15 Jul 2016 15:03:47 +0200 Subject: [PATCH 116/297] Added more logging --- NetworkPrinterOutputDevice.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 27a11916d4..065ac7c4f0 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -388,6 +388,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Handler for all requests that have finished. def _onFinished(self, reply): + status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if reply.operation() == QNetworkAccessManager.GetOperation: if "printer" in reply.url().toString(): # Status update from printer. if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: @@ -397,6 +398,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._spliceJSONData() else: + Logger.log("w", "We got an unexpected status (%s) while requesting printer state", status_code) pass # TODO: Handle errors elif "print_job" in reply.url().toString(): # Status update from print_job: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: @@ -416,6 +418,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setTimeElapsed(0) self.setTimeTotal(0) self.setJobName("") + else: + Logger.log("w", "We got an unexpected status (%s) while requesting print job state", status_code) elif "snapshot" in reply.url().toString(): # Status update from image: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: self._camera_image.loadFromData(reply.readAll()) From 479a155b39c4cd54a355b5ab041921e83fa110c2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 15 Jul 2016 15:05:02 +0200 Subject: [PATCH 117/297] Code cleanup --- NetworkPrinterOutputDevice.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 065ac7c4f0..c7750cd538 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -391,7 +391,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if reply.operation() == QNetworkAccessManager.GetOperation: if "printer" in reply.url().toString(): # Status update from printer. - if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: + if status_code == 200: if self._connection_state == ConnectionState.connecting: self.setConnectionState(ConnectionState.connected) self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8")) @@ -401,7 +401,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("w", "We got an unexpected status (%s) while requesting printer state", status_code) pass # TODO: Handle errors elif "print_job" in reply.url().toString(): # Status update from print_job: - if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: + if status_code == 200: json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) progress = json_data["progress"] ## If progress is 0 add a bit so another print can't be sent. @@ -412,7 +412,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) self.setJobName(json_data["name"]) - elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 404: + elif status_code == 404: self.setProgress(0) # No print job found, so there can't be progress or other data. self._updateJobState("") self.setTimeElapsed(0) @@ -421,18 +421,18 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): else: Logger.log("w", "We got an unexpected status (%s) while requesting print job state", status_code) elif "snapshot" in reply.url().toString(): # Status update from image: - if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: + if status_code == 200: self._camera_image.loadFromData(reply.readAll()) self.newImage.emit() elif "auth/verify" in reply.url().toString(): # Answer when requesting authentication - if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 401: + if status_code == 401: if self._authentication_state != AuthState.AuthenticationRequested: # Only request a new authentication when we have not already done so. Logger.log("i", "Not authenticated. Attempting to request authentication") self._requestAuthentication() - elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 403: + elif status_code == 403: pass - elif reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200: + elif status_code == 200: self.setAuthenticationState(AuthState.Authenticated) global_container_stack = Application.getInstance().getGlobalContainerStack() ## Save authentication details. @@ -478,8 +478,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() elif reply.operation() == QNetworkAccessManager.PutOperation: - if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 204: - pass # Request was sucesfull! + if status_code == 204: + pass # Request was successful! else: Logger.log("d", "Something went wrong when trying to update data of API (%s). Message: %s Statuscode: %s", reply.url().toString(), reply.readAll(), reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) else: From 01f051b82a7a51ea1c04c01df36531ea00129664 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 15 Jul 2016 15:10:57 +0200 Subject: [PATCH 118/297] Timeouts are now logged CURA-1851 --- NetworkPrinterOutputDevice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index c7750cd538..b22d36f41d 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -9,7 +9,7 @@ import UM.Settings from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState -from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager +from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot from PyQt5.QtGui import QImage @@ -388,6 +388,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Handler for all requests that have finished. def _onFinished(self, reply): + if reply.error() == QNetworkReply.TimeoutError: + Logger.log("w", "Received a timeout on a request to the printer") + return + status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if reply.operation() == QNetworkAccessManager.GetOperation: if "printer" in reply.url().toString(): # Status update from printer. From 0df2543f1a1e84baf768c9efc9a8e49696654fff Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 21 Jul 2016 11:58:35 +0200 Subject: [PATCH 119/297] Fixed timeout implementation Turned out that QT timeout wasn't triggred at all, so we're now keeping track of this with our own timer CURA-1851 --- NetworkPrinterOutputDevice.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index b22d36f41d..3001c1b41a 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -16,6 +16,8 @@ from PyQt5.QtGui import QImage import json import os +from time import time + i18n_catalog = i18nCatalog("cura") from enum import IntEnum @@ -104,6 +106,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._camera_image = QImage() self._material_post_objects = {} + self._connection_state_before_timeout = None + + self._last_response_time = time() + self._timeout_time = 10 def _onAuthenticationTimer(self): self._authentication_counter += 1 @@ -178,6 +184,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Request data from the connected device. def _update(self): + # Check that we aren't in a timeout state + if self._last_response_time and not self._connection_state_before_timeout: + if time() - self._last_response_time > self._timeout_time: + # Go into timeout state. + Logger.log("d", "We did not recieve a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) + self._connection_state_before_timeout = self._connection_state + self.setConnectionState(ConnectionState.error) + if self._authentication_state == AuthState.NotAuthenticated: self._verifyAuthentication() # We don't know if we are authenticated; check if we have correct auth. elif self._authentication_state == AuthState.AuthenticationRequested: @@ -390,8 +404,20 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _onFinished(self, reply): if reply.error() == QNetworkReply.TimeoutError: Logger.log("w", "Received a timeout on a request to the printer") + self._connection_state_before_timeout = self._connection_state + self.setConnectionState(ConnectionState.error) return + if self._connection_state_before_timeout and reply.error() == QNetworkReply.NoError: # There was a timeout, but we got a correct answer again. + Logger.log("d", "We got a response from the server after %s of silence", time() - self._last_response_time ) + self.setConnectionState(self._connection_state_before_timeout) + self._connection_state_before_timeout = None + + if reply.error() == QNetworkReply.NoError: + self._last_response_time = time() + else: + return # Error in the reply, drop it. + status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if reply.operation() == QNetworkAccessManager.GetOperation: if "printer" in reply.url().toString(): # Status update from printer. From e584275f7a66d45604f6aa8abced898349b56fbb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 25 Jul 2016 16:31:18 +0200 Subject: [PATCH 120/297] No longer drop messages that have an error state. I overlooked that authentication required is also an error state Fixes CURA-1975 --- NetworkPrinterOutputDevice.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 3001c1b41a..e593b0959b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -415,8 +415,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if reply.error() == QNetworkReply.NoError: self._last_response_time = time() - else: - return # Error in the reply, drop it. status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if reply.operation() == QNetworkAccessManager.GetOperation: From ea022204adee92155fc963884ec38a48411fb4fc Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 27 Jul 2016 13:10:57 +0200 Subject: [PATCH 121/297] Decreased timeout time to 5 sec --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e593b0959b..f91eaed05c 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -109,7 +109,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_state_before_timeout = None self._last_response_time = time() - self._timeout_time = 10 + self._response_timeout_time = 5 def _onAuthenticationTimer(self): self._authentication_counter += 1 @@ -186,7 +186,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _update(self): # Check that we aren't in a timeout state if self._last_response_time and not self._connection_state_before_timeout: - if time() - self._last_response_time > self._timeout_time: + if time() - self._last_response_time > self._response_timeout_time: # Go into timeout state. Logger.log("d", "We did not recieve a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) self._connection_state_before_timeout = self._connection_state From 3c2836f3f5d6a09f4ed3aa76fb0a7458a481958d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 27 Jul 2016 13:43:27 +0200 Subject: [PATCH 122/297] Connecting with a different machine resets the authentication data --- DiscoverUM3Action.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 27c5ef6638..df92e36cf8 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -38,8 +38,12 @@ class DiscoverUM3Action(MachineAction): def setKey(self, key): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: - if "um_network_key" in global_container_stack.getMetaData(): + meta_data = global_container_stack.getMetaData() + if "um_network_key" in meta_data: global_container_stack.setMetaDataEntry("um_network_key", key) + # Delete old authentication data. + global_container_stack.removeMetaDataEntry("network_authentication_id") + global_container_stack.removeMetaDataEntry("network_authentication_key") else: global_container_stack.addMetaDataEntry("um_network_key", key) From fd36f09b29cfc3ac000070f14d9b3e5aba2906cf Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 27 Jul 2016 14:14:43 +0200 Subject: [PATCH 123/297] "none" as state is now sent as empty string --- NetworkPrinterOutputDevice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f91eaed05c..11aa6992ef 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -436,7 +436,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if progress == 0: progress += 0.001 self.setProgress(progress * 100) - self._updateJobState(json_data["state"]) + + state = json_data["state"] + if state == "none": + state = "" + self._updateJobState(state) self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) self.setJobName(json_data["name"]) From 050e81053f27eac4d0e31675ec6e2aba4ceb6b24 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 27 Jul 2016 15:36:08 +0200 Subject: [PATCH 124/297] Double progress bar no longer occurs --- NetworkPrinterOutputDevice.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 11aa6992ef..109d3dd195 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -506,9 +506,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif "materials" in reply.url().toString(): # Remove cached post request items. del self._material_post_objects[id(reply)] - else: + elif "print_job" in reply.url().toString(): reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() + elif reply.operation() == QNetworkAccessManager.PutOperation: if status_code == 204: pass # Request was successful! @@ -519,6 +520,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _onUploadProgress(self, bytes_sent, bytes_total): if bytes_total > 0: - self._progress_message.setProgress(bytes_sent / bytes_total * 100) + new_progress = bytes_sent / bytes_total * 100 + if new_progress > self._progress_message.getProgress(): + self._progress_message.setProgress(bytes_sent / bytes_total * 100) else: self._progress_message.setProgress(0) \ No newline at end of file From 136755758e564f8eccc0b4ea0aa098252d05fbad Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 27 Jul 2016 16:41:06 +0200 Subject: [PATCH 125/297] Implement ServiceStateChange.Removed (bonjour undiscovery) Contributes to CURA-1851 --- DiscoverUM3Action.py | 13 +++++++------ NetworkPrinterOutputDevice.py | 6 ++++++ NetworkPrinterOutputDevicePlugin.py | 13 ++++++++++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index df92e36cf8..9ffb28c74d 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -14,19 +14,20 @@ class DiscoverUM3Action(MachineAction): self._network_plugin = None - printerDetected = pyqtSignal() + printersChanged = pyqtSignal() @pyqtSlot() def startDiscovery(self): if not self._network_plugin: self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("JediWifiPrintingPlugin") - self._network_plugin.addPrinterSignal.connect(self._onPrinterAdded) - self.printerDetected.emit() + self._network_plugin.addPrinterSignal.connect(self._onPrinterDiscoveryChanged) + self._network_plugin.removePrinterSignal.connect(self._onPrinterDiscoveryChanged) + self.printersChanged.emit() - def _onPrinterAdded(self, *args): - self.printerDetected.emit() + def _onPrinterDiscoveryChanged(self, *args): + self.printersChanged.emit() - @pyqtProperty("QVariantList", notify = printerDetected) + @pyqtProperty("QVariantList", notify = printersChanged) def foundDevices(self): if self._network_plugin: printers = self._network_plugin.getPrinters() diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 109d3dd195..71c8bf94f9 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -233,6 +233,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._updateHeadPosition(head_x, head_y, head_z) def close(self): + self._updateJobState("") self.setConnectionState(ConnectionState.closed) if self._progress_message: self._progress_message.hide() @@ -279,6 +280,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.start() self._camera_timer.start() + ## Stop requesting data from printer + def disconnect(self): + Logger.log("d", "Connection with printer %s with ip %s stopped", self._key, self._address) + self.close() + newImage = pyqtSignal() @pyqtProperty(QUrl, notify = newImage) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 4055b59a1a..5b5a04d9f3 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -19,9 +19,11 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addPrinterSignal.connect(self.addPrinter) + self.removePrinterSignal.connect(self.removePrinter) Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) addPrinterSignal = Signal() + removePrinterSignal = Signal() ## Start looking for devices on network. def start(self): @@ -56,6 +58,13 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._printers[printer.getKey()].connect() printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + def removePrinter(self, name): + printer = self._printers.pop(name, None) + if printer: + if printer.isConnected(): + printer.connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged) + printer.disconnect() + ## Handler for when the connection state of one of the detected printers changes def _onPrinterConnectionStateChanged(self, key): if self._printers[key].isConnected(): @@ -73,6 +82,4 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self.addPrinterSignal.emit(str(name), address, info.properties) elif state_change == ServiceStateChange.Removed: - pass - # TODO; This isn't testable right now. We need to also decide how to handle - # \ No newline at end of file + self.removePrinterSignal.emit(str(name)) \ No newline at end of file From 113129eaca8a421b838c3edb822210ea7b47ac05 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 28 Jul 2016 16:15:31 +0200 Subject: [PATCH 126/297] Fix typo --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 71c8bf94f9..94cc689940 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -188,7 +188,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._last_response_time and not self._connection_state_before_timeout: if time() - self._last_response_time > self._response_timeout_time: # Go into timeout state. - Logger.log("d", "We did not recieve a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) + Logger.log("d", "We did not receive a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) self._connection_state_before_timeout = self._connection_state self.setConnectionState(ConnectionState.error) From f0521291716b1c12164bc438fd0021b4d1a8d484 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 28 Jul 2016 18:09:26 +0200 Subject: [PATCH 127/297] Show a message to the user when the connection with the printer is lost CURA-1851 --- NetworkPrinterOutputDevice.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 94cc689940..28a25de4f8 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -77,6 +77,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message = None self._error_message = None + self._connection_message = None self._update_timer = QTimer() self._update_timer.setInterval(2000) # TODO; Add preference for update interval @@ -190,12 +191,15 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Go into timeout state. Logger.log("d", "We did not receive a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) self._connection_state_before_timeout = self._connection_state + self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost.Check your network-connections.")) + self._connection_message.show() self.setConnectionState(ConnectionState.error) if self._authentication_state == AuthState.NotAuthenticated: self._verifyAuthentication() # We don't know if we are authenticated; check if we have correct auth. elif self._authentication_state == AuthState.AuthenticationRequested: self._checkAuthentication() # We requested authentication at some point. Check if we got permission. + ## Request 'general' printer data url = QUrl("http://" + self._address + self._api_prefix + "printer") printer_request = QNetworkRequest(url) @@ -423,6 +427,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._last_response_time = time() status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) + if not status_code: + # Received no or empty reply + return + if reply.operation() == QNetworkAccessManager.GetOperation: if "printer" in reply.url().toString(): # Status update from printer. if status_code == 200: @@ -431,6 +439,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8")) self._spliceJSONData() + + # Hide connection error message if the connection was restored + if self._connection_message: + self._connection_message.hide() + self._connection_message = None else: Logger.log("w", "We got an unexpected status (%s) while requesting printer state", status_code) pass # TODO: Handle errors @@ -515,7 +528,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif "print_job" in reply.url().toString(): reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() - + elif reply.operation() == QNetworkAccessManager.PutOperation: if status_code == 204: pass # Request was successful! From b08bdb206bc51beadd56e0bdd4f4a45ed523206b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 28 Jul 2016 18:48:15 +0200 Subject: [PATCH 128/297] Added logging for bonjour discovery/undiscovery CURA-1851 --- NetworkPrinterOutputDevicePlugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 5b5a04d9f3..a36f7ea9eb 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -2,6 +2,7 @@ from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from . import NetworkPrinterOutputDevice from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange +from UM.Logger import Logger from UM.Signal import Signal, signalemitter from UM.Application import Application @@ -75,6 +76,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): ## Handler for zeroConf detection def _onServiceChanged(self, zeroconf, service_type, name, state_change): if state_change == ServiceStateChange.Added: + Logger.log("d", "Bonjour service added: %s" % name) info = zeroconf.get_service_info(service_type, name) if info: if info.properties.get(b"type", None) == b'printer': @@ -82,4 +84,5 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self.addPrinterSignal.emit(str(name), address, info.properties) elif state_change == ServiceStateChange.Removed: + Logger.log("d", "Bonjour service removed: %s" % name) self.removePrinterSignal.emit(str(name)) \ No newline at end of file From 6d94d3e1d904226cc2583c61cf3eda184f8e8e97 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 29 Jul 2016 11:25:45 +0200 Subject: [PATCH 129/297] Remove Ok/Cancel buttons to better fit in wizard or action dialog CURA-2019 --- DiscoverUM3Action.qml | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index d74a39ade7..3d57bb84d1 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -142,29 +142,19 @@ Cura.MachineAction text: base.selectedPrinter ? base.selectedPrinter.ipAddress : "" } } + + Button + { + text: catalog.i18nc("@action:button", "Ok") + enabled: base.selectedPrinter + onClicked: + { + manager.setKey(base.selectedPrinter.getKey()) + completed() + } + } + } } } - Button - { - text: catalog.i18nc("@action:button", "Ok") - anchors.right: cancelButton.left - anchors.bottom: parent.bottom - onClicked: - { - manager.setKey(base.selectedPrinter.getKey()) - completed() - } - } - Button - { - id: cancelButton - text: catalog.i18nc("@action:button", "Cancel") - anchors.right: discoverUM3Action.right - anchors.bottom: parent.bottom - onClicked: - { - completed() - } - } } \ No newline at end of file From 14624e7acba1ea4338d591e0cb48fa48bda138f2 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 29 Jul 2016 12:39:31 +0200 Subject: [PATCH 130/297] Change "Ok" button caption to "Connect" CURA-2019 --- DiscoverUM3Action.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 3d57bb84d1..ff8461ce21 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -145,7 +145,7 @@ Cura.MachineAction Button { - text: catalog.i18nc("@action:button", "Ok") + text: catalog.i18nc("@action:button", "Connect") enabled: base.selectedPrinter onClicked: { From 80f5ad3b913131d99f708cca0ca27be041efc7ab Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 29 Jul 2016 17:45:52 +0200 Subject: [PATCH 131/297] Codestyle & documentation --- NetworkPrinterOutputDevice.py | 36 +++++++++++------------------------ 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 28a25de4f8..096f6a3193 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -77,7 +77,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message = None self._error_message = None - self._connection_message = None self._update_timer = QTimer() self._update_timer.setInterval(2000) # TODO; Add preference for update interval @@ -111,6 +110,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._last_response_time = time() self._response_timeout_time = 5 + self._not_authenticated_message = None def _onAuthenticationTimer(self): self._authentication_counter += 1 @@ -189,16 +189,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._last_response_time and not self._connection_state_before_timeout: if time() - self._last_response_time > self._response_timeout_time: # Go into timeout state. - Logger.log("d", "We did not receive a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) + Logger.log("d", "We did not recieve a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) self._connection_state_before_timeout = self._connection_state - self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost.Check your network-connections.")) - self._connection_message.show() self.setConnectionState(ConnectionState.error) if self._authentication_state == AuthState.NotAuthenticated: - self._verifyAuthentication() # We don't know if we are authenticated; check if we have correct auth. + self._verifyAuthentication() # We don't know if we are authenticated; check if we have correct auth. elif self._authentication_state == AuthState.AuthenticationRequested: - self._checkAuthentication() # We requested authentication at some point. Check if we got permission. + self._checkAuthentication() # We requested authentication at some point. Check if we got permission. ## Request 'general' printer data url = QUrl("http://" + self._address + self._api_prefix + "printer") @@ -237,7 +235,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._updateHeadPosition(head_x, head_y, head_z) def close(self): - self._updateJobState("") self.setConnectionState(ConnectionState.closed) if self._progress_message: self._progress_message.hide() @@ -284,16 +281,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.start() self._camera_timer.start() - ## Stop requesting data from printer - def disconnect(self): - Logger.log("d", "Connection with printer %s with ip %s stopped", self._key, self._address) - self.close() - newImage = pyqtSignal() @pyqtProperty(QUrl, notify = newImage) def cameraImage(self): self._camera_image_id += 1 + # There is an image provider that is called "camera". In order to ensure that the image qml object, that + # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl + # as new (instead of relying on cached version and thus forces an update. temp = "image://camera/" + str(self._camera_image_id) return QUrl(temp, QUrl.TolerantMode) @@ -310,7 +305,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Convenience function to get the username from the OS. # The code was copied from the getpass module, as we try to use as little dependencies as possible. def _getUserName(self): - for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'): + for name in ("LOGNAME", "USER", "LNAME", "USERNAME"): user = os.environ.get(name) if user: return user @@ -418,8 +413,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setConnectionState(ConnectionState.error) return - if self._connection_state_before_timeout and reply.error() == QNetworkReply.NoError: # There was a timeout, but we got a correct answer again. - Logger.log("d", "We got a response from the server after %s of silence", time() - self._last_response_time ) + if self._connection_state_before_timeout and reply.error() == QNetworkReply.NoError: # There was a timeout, but we got a correct answer again. + Logger.log("d", "We got a response from the server after %s of silence", time() - self._last_response_time) self.setConnectionState(self._connection_state_before_timeout) self._connection_state_before_timeout = None @@ -427,10 +422,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._last_response_time = time() status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) - if not status_code: - # Received no or empty reply - return - if reply.operation() == QNetworkAccessManager.GetOperation: if "printer" in reply.url().toString(): # Status update from printer. if status_code == 200: @@ -439,11 +430,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8")) self._spliceJSONData() - - # Hide connection error message if the connection was restored - if self._connection_message: - self._connection_message.hide() - self._connection_message = None else: Logger.log("w", "We got an unexpected status (%s) while requesting printer state", status_code) pass # TODO: Handle errors @@ -543,4 +529,4 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if new_progress > self._progress_message.getProgress(): self._progress_message.setProgress(bytes_sent / bytes_total * 100) else: - self._progress_message.setProgress(0) \ No newline at end of file + self._progress_message.setProgress(0) From 6c0168c3ea1baf09a63a0b2cb5a659cc74290278 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 1 Aug 2016 15:23:23 +0200 Subject: [PATCH 132/297] Connect to selected printer when confirming the dialog CURA-2019 --- DiscoverUM3Action.qml | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index ff8461ce21..672b870c6b 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -11,6 +11,23 @@ Cura.MachineAction id: base anchors.fill: parent; property var selectedPrinter: null + + Connections + { + target: dialog ? dialog : null + ignoreUnknownSignals: true + onNextClicked: connectToPrinter() + } + + function connectToPrinter() + { + if(base.selectedPrinter) + { + manager.setKey(base.selectedPrinter.getKey()) + completed() + } + } + Column { anchors.fill: parent; @@ -57,7 +74,7 @@ Cura.MachineAction id: listview model: manager.foundDevices width: parent.width - currentIndex: activeIndex + currentIndex: -1 onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex] Component.onCompleted: manager.startDiscovery() delegate: Rectangle @@ -147,13 +164,8 @@ Cura.MachineAction { text: catalog.i18nc("@action:button", "Connect") enabled: base.selectedPrinter - onClicked: - { - manager.setKey(base.selectedPrinter.getKey()) - completed() - } + onClicked: connectToPrinter() } - } } } From c9daaf15212970e0565111f4f5efe1b127d0cbf4 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 1 Aug 2016 15:30:38 +0200 Subject: [PATCH 133/297] Reapply changes that were lost in 80f5ad3 --- NetworkPrinterOutputDevice.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 096f6a3193..99725e5a54 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -77,6 +77,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message = None self._error_message = None + self._connection_message = None self._update_timer = QTimer() self._update_timer.setInterval(2000) # TODO; Add preference for update interval @@ -189,8 +190,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._last_response_time and not self._connection_state_before_timeout: if time() - self._last_response_time > self._response_timeout_time: # Go into timeout state. - Logger.log("d", "We did not recieve a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) + Logger.log("d", "We did not receive a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) self._connection_state_before_timeout = self._connection_state + self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost.Check your network-connections.")) + self._connection_message.show() self.setConnectionState(ConnectionState.error) if self._authentication_state == AuthState.NotAuthenticated: @@ -235,6 +238,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._updateHeadPosition(head_x, head_y, head_z) def close(self): + self._updateJobState("") self.setConnectionState(ConnectionState.closed) if self._progress_message: self._progress_message.hide() @@ -281,6 +285,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.start() self._camera_timer.start() + ## Stop requesting data from printer + def disconnect(self): + Logger.log("d", "Connection with printer %s with ip %s stopped", self._key, self._address) + self.close() + newImage = pyqtSignal() @pyqtProperty(QUrl, notify = newImage) @@ -422,6 +431,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._last_response_time = time() status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) + if not status_code: + # Received no or empty reply + return + if reply.operation() == QNetworkAccessManager.GetOperation: if "printer" in reply.url().toString(): # Status update from printer. if status_code == 200: @@ -430,6 +443,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8")) self._spliceJSONData() + + # Hide connection error message if the connection was restored + if self._connection_message: + self._connection_message.hide() + self._connection_message = None else: Logger.log("w", "We got an unexpected status (%s) while requesting printer state", status_code) pass # TODO: Handle errors From d7f3b734203341cb3bc131f6e4c7aaecc43dc9c2 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 2 Aug 2016 10:31:49 +0200 Subject: [PATCH 134/297] Prevent infinite loop when adding a Jedi printer CURA-2019 --- DiscoverUM3Action.qml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 672b870c6b..6ccb078bbd 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -11,6 +11,7 @@ Cura.MachineAction id: base anchors.fill: parent; property var selectedPrinter: null + property var connectingWithPrinter: null Connections { @@ -23,8 +24,13 @@ Cura.MachineAction { if(base.selectedPrinter) { - manager.setKey(base.selectedPrinter.getKey()) - completed() + var printerKey = base.selectedPrinter.getKey() + if(connectingWithPrinter != printerKey) { + // prevent an infinite loop + connectingWithPrinter = printerKey; + manager.setKey(printerKey); + completed(); + } } } From bb8e74eb48299ae0eedf5710bfec07e685d628ed Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 2 Aug 2016 11:37:04 +0200 Subject: [PATCH 135/297] Fix connecting to the same printer twice CURA-2019 --- DiscoverUM3Action.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 6ccb078bbd..624b36b9bd 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -11,7 +11,7 @@ Cura.MachineAction id: base anchors.fill: parent; property var selectedPrinter: null - property var connectingWithPrinter: null + property var connectingToPrinter: null Connections { @@ -25,11 +25,14 @@ Cura.MachineAction if(base.selectedPrinter) { var printerKey = base.selectedPrinter.getKey() - if(connectingWithPrinter != printerKey) { + if(connectingToPrinter != printerKey) { // prevent an infinite loop - connectingWithPrinter = printerKey; + connectingToPrinter = printerKey; manager.setKey(printerKey); completed(); + } else { + // reset, so we can connect to the same printer again if needed + connectingToPrinter = null; } } } From 292826f2236e4fe32c4ef514293ca5044622c03e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 2 Aug 2016 14:18:01 +0200 Subject: [PATCH 136/297] Fix typo Missing space. --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 99725e5a54..ec0cbeca66 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -192,7 +192,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Go into timeout state. Logger.log("d", "We did not receive a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) self._connection_state_before_timeout = self._connection_state - self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost.Check your network-connections.")) + self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your network-connections.")) self._connection_message.show() self.setConnectionState(ConnectionState.error) From 805c513034d592cfca30c8a078702a2e423c9371 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 2 Aug 2016 16:23:33 +0200 Subject: [PATCH 137/297] Code cleanup CURA-2019 --- DiscoverUM3Action.qml | 3 --- 1 file changed, 3 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 624b36b9bd..d7992a1f5f 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -30,9 +30,6 @@ Cura.MachineAction connectingToPrinter = printerKey; manager.setKey(printerKey); completed(); - } else { - // reset, so we can connect to the same printer again if needed - connectingToPrinter = null; } } } From 10be9e66748bb8aa3628313f5b9606fad12dd0e9 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 3 Aug 2016 17:04:50 +0200 Subject: [PATCH 138/297] Fix an warning when a printer has a timeout before it has been fully discovered CURA-1851 --- NetworkPrinterOutputDevicePlugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index a36f7ea9eb..8db0c4a783 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -68,6 +68,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): ## Handler for when the connection state of one of the detected printers changes def _onPrinterConnectionStateChanged(self, key): + if key not in self._printers: + return if self._printers[key].isConnected(): self.getOutputDeviceManager().addOutputDevice(self._printers[key]) else: From 0b1c4ea5519e1cabcba9accc2012f8034dca3e70 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 3 Aug 2016 17:05:20 +0200 Subject: [PATCH 139/297] Make sure the output device gets selected above local file output --- NetworkPrinterOutputDevice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index ec0cbeca66..37542a74e6 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -37,6 +37,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._key = key self._properties = properties # Properties dict as provided by zero conf + self.setPriority(2) # Make sure the output device gets selected above local file output + self._gcode = None # This holds the full JSON file that was received from the last request. From c4850a6ff22ce067ead3622c5782e37ac39e94d4 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 4 Aug 2016 12:57:13 +0200 Subject: [PATCH 140/297] Show error state after aborting a print CURA-1990 --- NetworkPrinterOutputDevice.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 37542a74e6..e64a86f9f2 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -37,9 +37,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._key = key self._properties = properties # Properties dict as provided by zero conf - self.setPriority(2) # Make sure the output device gets selected above local file output - self._gcode = None + self._print_finished = True # _print_finsihed == False means we're halfway in a print # This holds the full JSON file that was received from the last request. self._json_printer_state = None @@ -57,6 +56,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" + self.setPriority(2) # Make sure the output device gets selected above local file output self.setName(key) self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print over network")) self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network")) @@ -254,6 +254,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def requestWrite(self, node, file_name = None, filter_by_machine = False): Application.getInstance().showPrintMonitor.emit(True) + self._print_finished = True self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") # TODO: Implement all checks. @@ -460,11 +461,27 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## If progress is 0 add a bit so another print can't be sent. if progress == 0: progress += 0.001 + elif progress == 1: + self._print_finished = True + else: + self._print_finished = False self.setProgress(progress * 100) state = json_data["state"] - if state == "none": - state = "" + + # There is a short period after aborting or finishing a print where the printer + # reports a "none" state (but the printer is not ready to receive a print) + # If this happens before the print has reached progress == 1, the print has + # been aborted. + if state == "none" or state == "": + if self._print_finished: + state = "printing" + else: + state = "error" + if state == "wait_cleanup" and not self._print_finished: + # Keep showing the "aborted" error state until after the buildplate has been cleaned + state = "error" + self._updateJobState(state) self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) From 31861d82dcc81b26986750688b226987e023ce63 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 4 Aug 2016 13:27:42 +0200 Subject: [PATCH 141/297] Show message while aborting CURA-1990 --- NetworkPrinterOutputDevice.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e64a86f9f2..eda865f7ca 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -477,9 +477,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._print_finished: state = "printing" else: + self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Aborting print...")) state = "error" if state == "wait_cleanup" and not self._print_finished: # Keep showing the "aborted" error state until after the buildplate has been cleaned + self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Print aborted. Please check the printer")) state = "error" self._updateJobState(state) @@ -489,6 +491,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif status_code == 404: self.setProgress(0) # No print job found, so there can't be progress or other data. self._updateJobState("") + self.setErrorText("") self.setTimeElapsed(0) self.setTimeTotal(0) self.setJobName("") From ef36c70d83cfbdbbe751516c92b960a92a9326f4 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 4 Aug 2016 14:23:57 +0200 Subject: [PATCH 142/297] Check if the printer is ready to receive *before* doing work. This has very little to do with CURA-1990 --- NetworkPrinterOutputDevice.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index eda865f7ca..4fc3a96276 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -253,6 +253,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._camera_timer.stop() def requestWrite(self, node, file_name = None, filter_by_machine = False): + if self._progress != 0: + self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) + self._error_message.show() + return + elif self._authentication_state != AuthState.Authenticated: + self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", + "Not authenticated to print with this machine. Unable to start a new job.")) + self._not_authenticated_message.show() + return + Application.getInstance().showPrintMonitor.emit(True) self._print_finished = True self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") @@ -327,15 +337,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # This function can fail to actually start a print due to not being authenticated or another print already # being in progress. def startPrint(self): - if self._progress != 0: - self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) - self._error_message.show() - return - elif self._authentication_state != AuthState.Authenticated: - self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", - "Not authenticated to print with this machine. Unable to start a new job.")) - self._not_authenticated_message.show() - return try: self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) self._progress_message.show() From d3333540de1307cca0b3113ef38978adc06e3799 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 4 Aug 2016 18:16:05 +0200 Subject: [PATCH 143/297] Show printer in "indeterminate" state when we are not authenticated CURA-1851 --- NetworkPrinterOutputDevice.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 4fc3a96276..35425643db 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -162,9 +162,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # \param auth_state \type{AuthState} Enum value representing the new auth state def setAuthenticationState(self, auth_state): if auth_state == AuthState.AuthenticationRequested: + self.setAcceptsCommands(False) self._authentication_requested_message.show() self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: + self.setAcceptsCommands(True) self._authentication_requested_message.hide() authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Printer was successfully paired with Cura")) authentication_succeeded_message.show() @@ -176,6 +178,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Once we are authenticated we need to send all material profiles. self.sendMaterialProfiles() elif auth_state == AuthState.AuthenticationDenied: + self.setAcceptsCommands(False) self._authentication_requested_message.hide() authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed. This can be either due to a timeout or the printer refused the request.")) authentication_failed_message.show() From 70a93ac0a26973142ab6832b63a65bcd48d268c4 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Aug 2016 10:27:19 +0200 Subject: [PATCH 144/297] Only associate to a Jedi if the MachineAction is currently shown CURA-2041 --- DiscoverUM3Action.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index d7992a1f5f..e679413770 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -17,7 +17,14 @@ Cura.MachineAction { target: dialog ? dialog : null ignoreUnknownSignals: true - onNextClicked: connectToPrinter() + onNextClicked: + { + // Connect to the printer if the MachineAction is currently shown + if(base.parent == dialog) + { + connectToPrinter(); + } + } } function connectToPrinter() From a288ab321fdf6987b742c44646774be32d8c3e3b Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 9 Aug 2016 11:19:39 +0200 Subject: [PATCH 145/297] Display an error message when "Print over network" if printer not idle. CURA-1986 --- NetworkPrinterOutputDevice.py | 54 +++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 35425643db..758fd9216a 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -40,8 +40,28 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._gcode = None self._print_finished = True # _print_finsihed == False means we're halfway in a print - # This holds the full JSON file that was received from the last request. - self._json_printer_state = None + # This holds the full JSON file that was received from the last request. + # The JSON looks like: + # {'led': {'saturation': 0.0, 'brightness': 100.0, 'hue': 0.0}, + # 'beep': {}, 'network': {'wifi_networks': [], + # 'ethernet': {'connected': True, 'enabled': True}, + # 'wifi': {'ssid': 'xxxx', 'connected': False, 'enabled': False}}, + # 'diagnostics': {}, + # 'bed': {'temperature': {'target': 60.0, 'current': 44.4}}, + # 'heads': [{'max_speed': {'z': 40.0, 'y': 300.0, 'x': 300.0}, + # 'position': {'z': 20.0, 'y': 6.0, 'x': 180.0}, + # 'fan': 0.0, + # 'jerk': {'z': 0.4, 'y': 20.0, 'x': 20.0}, + # 'extruders': [ + # {'feeder': {'max_speed': 45.0, 'jerk': 5.0, 'acceleration': 3000.0}, + # 'active_material': {'GUID': 'xxxxxxx', 'length_remaining': -1.0}, + # 'hotend': {'temperature': {'target': 0.0, 'current': 22.8}, 'id': 'AA 0.4'}}, + # {'feeder': {'max_speed': 45.0, 'jerk': 5.0, 'acceleration': 3000.0}, + # 'active_material': {'GUID': 'xxxx', 'length_remaining': -1.0}, + # 'hotend': {'temperature': {'target': 0.0, 'current': 22.8}, 'id': 'BB 0.4'}}], + # 'acceleration': 3000.0}], + # 'status': 'printing'} + self._json_printer_state = {} ## Todo: Hardcoded value now; we should probably read this from the machine file. ## It's okay to leave this for now, as this plugin is um3 only (and has 2 extruders by definition) @@ -73,7 +93,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._post_multi_part = None self._post_part = None - self._material_multi_part = None self._material_part = None @@ -195,7 +214,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._last_response_time and not self._connection_state_before_timeout: if time() - self._last_response_time > self._response_timeout_time: # Go into timeout state. - Logger.log("d", "We did not receive a response for %s seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) + Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) self._connection_state_before_timeout = self._connection_state self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your network-connections.")) self._connection_message.show() @@ -260,6 +279,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) self._error_message.show() return + if self._json_printer_state["status"] != "idle": + self._error_message = Message( + i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is not idle. Current printer status is %s.") % self._json_printer_state["status"]) + self._error_message.show() + return elif self._authentication_state != AuthState.Authenticated: self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", "Not authenticated to print with this machine. Unable to start a new job.")) @@ -430,7 +454,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return if self._connection_state_before_timeout and reply.error() == QNetworkReply.NoError: # There was a timeout, but we got a correct answer again. - Logger.log("d", "We got a response from the server after %s of silence", time() - self._last_response_time) + Logger.log("d", "We got a response from the server after %0.1f of silence", time() - self._last_response_time) self.setConnectionState(self._connection_state_before_timeout) self._connection_state_before_timeout = None @@ -441,14 +465,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if not status_code: # Received no or empty reply return + reply_url = reply.url().toString() if reply.operation() == QNetworkAccessManager.GetOperation: - if "printer" in reply.url().toString(): # Status update from printer. + if "printer" in reply_url: # Status update from printer. if status_code == 200: if self._connection_state == ConnectionState.connecting: self.setConnectionState(ConnectionState.connected) self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8")) - self._spliceJSONData() # Hide connection error message if the connection was restored @@ -458,7 +482,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): else: Logger.log("w", "We got an unexpected status (%s) while requesting printer state", status_code) pass # TODO: Handle errors - elif "print_job" in reply.url().toString(): # Status update from print_job: + elif "print_job" in reply_url: # Status update from print_job: if status_code == 200: json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) progress = json_data["progress"] @@ -501,11 +525,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setJobName("") else: Logger.log("w", "We got an unexpected status (%s) while requesting print job state", status_code) - elif "snapshot" in reply.url().toString(): # Status update from image: + elif "snapshot" in reply_url: # Status update from image: if status_code == 200: self._camera_image.loadFromData(reply.readAll()) self.newImage.emit() - elif "auth/verify" in reply.url().toString(): # Answer when requesting authentication + elif "auth/verify" in reply_url: # Answer when requesting authentication if status_code == 401: if self._authentication_state != AuthState.AuthenticationRequested: # Only request a new authentication when we have not already done so. @@ -531,7 +555,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("w", "While trying to authenticate, we got an unexpected response: %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) self.setAuthenticationState(AuthState.NotAuthenticated) - elif "auth/check" in reply.url().toString(): # Check if we are authenticated (user can refuse this!) + elif "auth/check" in reply_url: # Check if we are authenticated (user can refuse this!) data = json.loads(bytes(reply.readAll()).decode("utf-8")) if data.get("message", "") == "authorized": Logger.log("i", "Authentication was approved") @@ -543,7 +567,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass elif reply.operation() == QNetworkAccessManager.PostOperation: - if "/auth/request" in reply.url().toString(): + if "/auth/request" in reply_url: # We got a response to requesting authentication. data = json.loads(bytes(reply.readAll()).decode("utf-8")) self._authentication_key = data["key"] @@ -552,10 +576,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Check if the authentication is accepted. self._checkAuthentication() - elif "materials" in reply.url().toString(): + elif "materials" in reply_url: # Remove cached post request items. del self._material_post_objects[id(reply)] - elif "print_job" in reply.url().toString(): + elif "print_job" in reply_url: reply.uploadProgress.disconnect(self._onUploadProgress) self._progress_message.hide() @@ -563,7 +587,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if status_code == 204: pass # Request was successful! else: - Logger.log("d", "Something went wrong when trying to update data of API (%s). Message: %s Statuscode: %s", reply.url().toString(), reply.readAll(), reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) + Logger.log("d", "Something went wrong when trying to update data of API (%s). Message: %s Statuscode: %s", reply_url, reply.readAll(), status_code) else: Logger.log("d", "NetworkPrinterOutputDevice got an unhandled operation %s", reply.operation()) From 3a0b9ccafe94724c6ddbeef85a68a0e6d0f8a4b6 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 9 Aug 2016 11:22:24 +0200 Subject: [PATCH 146/297] Typo CURA-1986 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 758fd9216a..22b14e3c23 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -214,7 +214,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._last_response_time and not self._connection_state_before_timeout: if time() - self._last_response_time > self._response_timeout_time: # Go into timeout state. - Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accesible.", time() - self._last_response_time) + Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accessible.", time() - self._last_response_time) self._connection_state_before_timeout = self._connection_state self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your network-connections.")) self._connection_message.show() From a0535a7aaf783e2307a7b39d9c6230344455a31f Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Aug 2016 11:22:26 +0200 Subject: [PATCH 147/297] Add some logging to debug discovery issues CURA-2035 --- NetworkPrinterOutputDevicePlugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 8db0c4a783..b9d8cebadf 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -84,6 +84,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if info.properties.get(b"type", None) == b'printer': address = '.'.join(map(lambda n: str(n), info.address)) self.addPrinterSignal.emit(str(name), address, info.properties) + else: + Logger.log("w", "Could not get information about %s" % name) elif state_change == ServiceStateChange.Removed: Logger.log("d", "Bonjour service removed: %s" % name) From 86a65e495bb407127ef3fa681fbb3baafe8c9078 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 9 Aug 2016 13:10:13 +0200 Subject: [PATCH 148/297] Close now resets authentication & timeout data. CURA-1936 --- NetworkPrinterOutputDevice.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 22b14e3c23..60188921f8 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -266,11 +266,21 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setConnectionState(ConnectionState.closed) if self._progress_message: self._progress_message.hide() + + # Reset authentication state self._authentication_requested_message.hide() - if self._error_message: - self._error_message.hide() + self._authentication_state = AuthState.NotAuthenticated self._authentication_counter = 0 self._authentication_timer.stop() + + if self._error_message: + self._error_message.hide() + + # Reset timeout state + self._connection_state_before_timeout = None + self._last_response_time = time() + + # Stop update timers self._update_timer.stop() self._camera_timer.stop() @@ -313,6 +323,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Start requesting data from printer def connect(self): + self.close() # Ensure that previous connection (if any) is killed. self.setConnectionState(ConnectionState.connecting) self._update() # Manually trigger the first update, as we don't want to wait a few secs before it starts. self._update_camera() From a649bdcb325acd31ea82988427ccbecbc0d5d2e6 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Aug 2016 13:49:47 +0200 Subject: [PATCH 149/297] Speed up Bonjour discovery and make it slightly more robust CURA-2035 --- NetworkPrinterOutputDevicePlugin.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index b9d8cebadf..2ded3b48e7 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -1,11 +1,12 @@ from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from . import NetworkPrinterOutputDevice -from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange +from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo from UM.Logger import Logger from UM.Signal import Signal, signalemitter from UM.Application import Application +import time ## This plugin handles the connection detection & creation of output device objects for the UM3 printer. # Zero-Conf is used to detect printers, which are saved in a dict. @@ -79,7 +80,20 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): def _onServiceChanged(self, zeroconf, service_type, name, state_change): if state_change == ServiceStateChange.Added: Logger.log("d", "Bonjour service added: %s" % name) - info = zeroconf.get_service_info(service_type, name) + + info = ServiceInfo(service_type, name, properties = {}) + for record in zeroconf.cache.entries_with_name(name.lower()): + info.update_record(zeroconf, time.time(), record) + + for record in zeroconf.cache.entries_with_name(info.server): + info.update_record(zeroconf, time.time(), record) + if info.address: + break + + if not info.address: + Logger.log("d", "Trying to get address of %s", name) + info = zeroconf.get_service_info(service_type, name) + if info: if info.properties.get(b"type", None) == b'printer': address = '.'.join(map(lambda n: str(n), info.address)) From bd6e36f4870cb268557922a45033e424915b3b3c Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Aug 2016 13:54:13 +0200 Subject: [PATCH 150/297] Add documentation CURA-2035 --- NetworkPrinterOutputDevicePlugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 2ded3b48e7..6fada8364a 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -81,6 +81,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if state_change == ServiceStateChange.Added: Logger.log("d", "Bonjour service added: %s" % name) + # First try getting info from zeroconf cache info = ServiceInfo(service_type, name, properties = {}) for record in zeroconf.cache.entries_with_name(name.lower()): info.update_record(zeroconf, time.time(), record) @@ -90,6 +91,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if info.address: break + # Request more data if info is not complete if not info.address: Logger.log("d", "Trying to get address of %s", name) info = zeroconf.get_service_info(service_type, name) From 7f83c22f741d4902f4cbb48a5f5bb5c725404cc7 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Aug 2016 15:18:57 +0200 Subject: [PATCH 151/297] Add "refresh" button to list of network printers CURA-2035 --- DiscoverUM3Action.py | 3 +++ DiscoverUM3Action.qml | 13 ++++++++++++- NetworkPrinterOutputDevicePlugin.py | 9 +++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 9ffb28c74d..c24f90674f 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -23,6 +23,9 @@ class DiscoverUM3Action(MachineAction): self._network_plugin.addPrinterSignal.connect(self._onPrinterDiscoveryChanged) self._network_plugin.removePrinterSignal.connect(self._onPrinterDiscoveryChanged) self.printersChanged.emit() + else: + # Restart bonjour discovery + self._network_plugin.startDiscovery() def _onPrinterDiscoveryChanged(self, *args): self.printersChanged.emit() diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index e679413770..2a70457f7c 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -45,6 +45,8 @@ Cura.MachineAction { anchors.fill: parent; id: discoverUM3Action + spacing: UM.Theme.getSize("default_margin").height + SystemPalette { id: palette } UM.I18nCatalog { id: catalog; name:"cura" } Label @@ -61,7 +63,16 @@ Cura.MachineAction id: pageDescription width: parent.width wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. \n\nIf you don't want to connect Cura with your Ultimaker 3 now, you can always use a USB drive to transfer g-code files to your printer.\n\nSelect your Ultimaker 3 from the list below:") + text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your Ultimaker 3, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your Ultimaker 3 from the list below:") + } + + Button + { + id: rediscoverButton + text: catalog.i18nc("@title", "Refresh") + onClicked: manager.startDiscovery() + anchors.right: parent.right + anchors.rightMargin: parent.width * 0.5 } Row diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 6fada8364a..dbb40e4d77 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -29,6 +29,15 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): ## Start looking for devices on network. def start(self): + self.startDiscovery() + + def startDiscovery(self): + if self._browser: + self._browser.cancel() + self._browser = None + self._printers = {} + self._zero_conf.__init__() + self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged]) ## Stop looking for devices on network. From 7e8eb2044afc1c374d10353a78ea2b774fccdbcf Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Aug 2016 15:19:58 +0200 Subject: [PATCH 152/297] Sort list of discovered printers CURA-2035 --- DiscoverUM3Action.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index c24f90674f..fc895a78ff 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -33,8 +33,9 @@ class DiscoverUM3Action(MachineAction): @pyqtProperty("QVariantList", notify = printersChanged) def foundDevices(self): if self._network_plugin: - printers = self._network_plugin.getPrinters() - return list(printers.values()) + printers = list(self._network_plugin.getPrinters().values()) + printers.sort(key = lambda k: k.name) + return printers else: return [] From 70115208459f606337beeed59c54c068fbfd0682 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Aug 2016 15:54:20 +0200 Subject: [PATCH 153/297] Select the UM3 that we are connected to if it is in the list CURA-2035 --- DiscoverUM3Action.py | 10 ++++++++++ DiscoverUM3Action.qml | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index fc895a78ff..4ca500dc48 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -55,3 +55,13 @@ class DiscoverUM3Action(MachineAction): if self._network_plugin: # Ensure that the connection states are refreshed. self._network_plugin.reCheckConnections() + + @pyqtSlot(result = str) + def getStoredKey(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + meta_data = global_container_stack.getMetaData() + if "um_network_key" in meta_data: + return global_container_stack.getMetaDataEntry("um_network_key") + + return "" \ No newline at end of file diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 2a70457f7c..4dcb5e074d 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -97,6 +97,18 @@ Cura.MachineAction { id: listview model: manager.foundDevices + onModelChanged: + { + var selectedKey = manager.getStoredKey(); + for(var i = 0; i < model.length; i++) { + if(model[i].getKey() == selectedKey) + { + currentIndex = i; + return + } + } + currentIndex = -1; + } width: parent.width currentIndex: -1 onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex] @@ -134,6 +146,7 @@ Cura.MachineAction { width: parent.width * 0.5 visible: base.selectedPrinter + spacing: UM.Theme.getSize("default_margin").height Label { width: parent.width From 8fbc85dd558e655b64b3d270500ca499f74fe6bb Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 9 Aug 2016 18:44:14 +0200 Subject: [PATCH 154/297] Add a connection string to the printmonitor CURA-2091 --- NetworkPrinterOutputDevice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 60188921f8..feaa71e393 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -182,10 +182,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def setAuthenticationState(self, auth_state): if auth_state == AuthState.AuthenticationRequested: self.setAcceptsCommands(False) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0} without access to control the printer.").format(self.name)) self._authentication_requested_message.show() self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: self.setAcceptsCommands(True) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}.").format(self.name)) self._authentication_requested_message.hide() authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Printer was successfully paired with Cura")) authentication_succeeded_message.show() From bd7e8e4c52bbc41eb5306648140b40ff4c97159f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 10 Aug 2016 10:46:02 +0200 Subject: [PATCH 155/297] Progress is now hidden when connection is lost CURA-1851 --- NetworkPrinterOutputDevice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index feaa71e393..cfcc5affc3 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -608,6 +608,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if bytes_total > 0: new_progress = bytes_sent / bytes_total * 100 if new_progress > self._progress_message.getProgress(): + self._progress_message.show() # Ensure that the message is visible. self._progress_message.setProgress(bytes_sent / bytes_total * 100) else: self._progress_message.setProgress(0) + self._progress_message.hide() From 925795401f0411526346f4cb037fe8f45a8f1df3 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 10 Aug 2016 17:36:26 +0200 Subject: [PATCH 156/297] Fix for abort network printer during pre_print. CURA-2093 --- NetworkPrinterOutputDevice.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 60188921f8..e721721474 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -134,6 +134,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._response_timeout_time = 5 self._not_authenticated_message = None + self._last_command = "" + def _onAuthenticationTimer(self): self._authentication_counter += 1 self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100) @@ -356,6 +358,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return self._camera_image def _setJobState(self, job_state): + self._last_command = job_state url = QUrl("http://" + self._address + self._api_prefix + "print_job/state") put_request = QNetworkRequest(url) put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") @@ -513,12 +516,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # If this happens before the print has reached progress == 1, the print has # been aborted. if state == "none" or state == "": - if self._print_finished: - state = "printing" - else: + if self._last_command == "abort": self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Aborting print...")) state = "error" - if state == "wait_cleanup" and not self._print_finished: + else: + state = "printing" + if state == "wait_cleanup" and self._last_command == "abort": # Keep showing the "aborted" error state until after the buildplate has been cleaned self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Print aborted. Please check the printer")) state = "error" From 2bbeeb3389a2f699f342b0aa361d17dd486bfaa2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 11 Aug 2016 10:36:21 +0200 Subject: [PATCH 157/297] There is now a hard and a soft discovery mode CURA-2086 --- DiscoverUM3Action.py | 6 +++++- DiscoverUM3Action.qml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 4ca500dc48..7115822582 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -23,8 +23,12 @@ class DiscoverUM3Action(MachineAction): self._network_plugin.addPrinterSignal.connect(self._onPrinterDiscoveryChanged) self._network_plugin.removePrinterSignal.connect(self._onPrinterDiscoveryChanged) self.printersChanged.emit() + + @pyqtSlot() + def restartDiscovery(self): + if not self._network_plugin: + self.startDiscovery() else: - # Restart bonjour discovery self._network_plugin.startDiscovery() def _onPrinterDiscoveryChanged(self, *args): diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 4dcb5e074d..dcfd76792b 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -70,7 +70,7 @@ Cura.MachineAction { id: rediscoverButton text: catalog.i18nc("@title", "Refresh") - onClicked: manager.startDiscovery() + onClicked: manager.restartDiscovery() anchors.right: parent.right anchors.rightMargin: parent.width * 0.5 } From d0a2b07587a8bc102ba647d8fcc33cb045981237 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 11 Aug 2016 10:45:01 +0200 Subject: [PATCH 158/297] Re-scan no longer forces a re-connect CURA-2086 --- NetworkPrinterOutputDevicePlugin.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index dbb40e4d77..bd930493be 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -19,6 +19,10 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._browser = None self._printers = {} + # List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces + # authentication requests. + self._old_printers = [] + # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addPrinterSignal.connect(self.addPrinter) self.removePrinterSignal.connect(self.removePrinter) @@ -35,6 +39,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if self._browser: self._browser.cancel() self._browser = None + self._old_printers = [printer_name for printer_name in self._printers] self._printers = {} self._zero_conf.__init__() @@ -66,8 +71,9 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._printers[printer.getKey()] = printer global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"): - self._printers[printer.getKey()].connect() - printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + if printer.getKey() not in self._old_printers: # Was the printer already connected, but a re-scan forced? + self._printers[printer.getKey()].connect() + printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) def removePrinter(self, name): printer = self._printers.pop(name, None) From 842d4b9ad86f64d6fb88aaa13eb191392d203f62 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 11 Aug 2016 15:34:37 +0200 Subject: [PATCH 159/297] Added retry action to auth failed message CURA-2086 --- NetworkPrinterOutputDevice.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 0c34a9ffcc..b100dfd7db 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -125,6 +125,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_key = None self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Requested access. Please aprove the request on the printer"), lifetime = 0, dismissable = False, progress = 0) + self._authentication_failed_message = None + self._camera_image = QImage() self._material_post_objects = {} @@ -203,8 +205,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif auth_state == AuthState.AuthenticationDenied: self.setAcceptsCommands(False) self._authentication_requested_message.hide() - authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed. This can be either due to a timeout or the printer refused the request.")) - authentication_failed_message.show() + self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed. This can be either due to a timeout or the printer refused the request.")) + self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry "), None, "Re-send the authentication request") + self._authentication_failed_message.actionTriggered.connect(self.messageActionTriggered) + self._authentication_failed_message.show() # Stop waiting for a response self._authentication_timer.stop() @@ -212,6 +216,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_state = auth_state + def messageActionTriggered(self, message_id, action_id): + self._authentication_failed_message.hide() + self._authentication_state = AuthState.NotAuthenticated + self._authentication_counter = 0 + self._authentication_requested_message.setProgress(0) + self._authentication_id = None + self._authentication_key = None + ## Request data from the connected device. def _update(self): # Check that we aren't in a timeout state From d7c6f85bcdec33e5b779026576e69edb22518c35 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 11 Aug 2016 15:53:45 +0200 Subject: [PATCH 160/297] Add i18n to tooltip and tweak pair request failed message CURA-2086 --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index b100dfd7db..e5bf330654 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -205,8 +205,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif auth_state == AuthState.AuthenticationDenied: self.setAcceptsCommands(False) self._authentication_requested_message.hide() - self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed. This can be either due to a timeout or the printer refused the request.")) - self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry "), None, "Re-send the authentication request") + self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed due to a timeout or the printer refused the request.")) + self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry "), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the authentication request")) self._authentication_failed_message.actionTriggered.connect(self.messageActionTriggered) self._authentication_failed_message.show() From c362c2e0b9bdcc4ac4935cadfcc7a4bb620e9a95 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Mon, 15 Aug 2016 11:04:26 +0200 Subject: [PATCH 161/297] Correct spelling --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e5bf330654..9405c9a629 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -124,7 +124,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_id = None self._authentication_key = None - self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Requested access. Please aprove the request on the printer"), lifetime = 0, dismissable = False, progress = 0) + self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Requested access. Please approve the request on the printer"), lifetime = 0, dismissable = False, progress = 0) self._authentication_failed_message = None self._camera_image = QImage() From dd649580f5a71a3a067e1c5a08734bcb712f9fcd Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 16 Aug 2016 16:46:35 +0200 Subject: [PATCH 162/297] Move the "Changes on Printer" dialog to JediWifiPrinting ...to have full control over the terminology CURA-2116 --- NetworkPrinterOutputDevice.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e5bf330654..66db24bc85 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -12,6 +12,7 @@ from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot from PyQt5.QtGui import QImage +from PyQt5.QtWidgets import QMessageBox import json import os @@ -628,3 +629,15 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): else: self._progress_message.setProgress(0) self._progress_message.hide() + + ## Let the user decide if the hotends and/or material should be synced with the printer + def materialHotendChangedMessage(self, callback): + Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Changes on the Printer"), + i18n_catalog.i18nc("@label", + "Do you want to change the PrintCores and materials to match your printer?"), + i18n_catalog.i18nc("@label", + "The materials and / or hotends on your printer were changed. For best results always slice for the PrintCores and materials that are inserted in your printer."), + buttons=QMessageBox.Yes + QMessageBox.No, + icon=QMessageBox.Question, + callback=callback + ) \ No newline at end of file From 631d079e264d1d33362142fb9c66817cfd1fb66c Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 16 Aug 2016 17:57:07 +0200 Subject: [PATCH 163/297] Show only one "Successfully paired" message at once. --- NetworkPrinterOutputDevice.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 1e2487ba9d..66611a3a4a 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -126,7 +126,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_key = None self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Requested access. Please approve the request on the printer"), lifetime = 0, dismissable = False, progress = 0) - self._authentication_failed_message = None + self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed due to a timeout or the printer refused the request.")) + self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry "), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the authentication request")) + self._authentication_failed_message.actionTriggered.connect(self.messageActionTriggered) + self._authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Printer was successfully paired with Cura")) self._camera_image = QImage() @@ -194,8 +197,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setAcceptsCommands(True) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}.").format(self.name)) self._authentication_requested_message.hide() - authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Printer was successfully paired with Cura")) - authentication_succeeded_message.show() + self._authentication_succeeded_message.show() # Stop waiting for a response self._authentication_timer.stop() @@ -206,9 +208,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif auth_state == AuthState.AuthenticationDenied: self.setAcceptsCommands(False) self._authentication_requested_message.hide() - self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed due to a timeout or the printer refused the request.")) - self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry "), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the authentication request")) - self._authentication_failed_message.actionTriggered.connect(self.messageActionTriggered) self._authentication_failed_message.show() # Stop waiting for a response @@ -290,6 +289,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_counter = 0 self._authentication_timer.stop() + self._authentication_requested_message.hide() + self._authentication_failed_message.hide() + self._authentication_succeeded_message.hide() + if self._error_message: self._error_message.hide() From 58115ce798296bb43155df993a847f688230d47e Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 17 Aug 2016 11:07:39 +0200 Subject: [PATCH 164/297] Fixed one more occurrence of "hotend" to "PrintCore" CURA-2116 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 66611a3a4a..96fb3f050d 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -639,7 +639,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): i18n_catalog.i18nc("@label", "Do you want to change the PrintCores and materials to match your printer?"), i18n_catalog.i18nc("@label", - "The materials and / or hotends on your printer were changed. For best results always slice for the PrintCores and materials that are inserted in your printer."), + "The PrintCores and/or materials on your printer were changed. For best results always slice for the PrintCores and materials that are inserted in your printer."), buttons=QMessageBox.Yes + QMessageBox.No, icon=QMessageBox.Question, callback=callback From 8cc9e8cf723169511b3a15f307e2db97eaa2c146 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Aug 2016 12:38:51 +0200 Subject: [PATCH 165/297] Post requests are now aborted when connection is timed out CURA-1851 --- NetworkPrinterOutputDevice.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 96fb3f050d..93fedf0c85 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -482,6 +482,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if reply.error() == QNetworkReply.TimeoutError: Logger.log("w", "Received a timeout on a request to the printer") self._connection_state_before_timeout = self._connection_state + # Check if we were uploading something. Abort if this is the case. + # Some operating systems handle this themselves, others give weird issues. + if self._post_reply: + self._post_reply.abort() self.setConnectionState(ConnectionState.error) return From d405afb9f26493b592d66b645d1c13a95cde4a43 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Aug 2016 12:40:35 +0200 Subject: [PATCH 166/297] Added bit more logging --- NetworkPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 93fedf0c85..ac8fe7fa78 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -499,6 +499,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if not status_code: + Logger.log("d", "A reply without a status code was received. Dropping the message") # Received no or empty reply return reply_url = reply.url().toString() From 26eaedaa2cebca06deabdbe3a638c865a8680b0b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Aug 2016 13:00:07 +0200 Subject: [PATCH 167/297] Improved verbosity of logging --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index ac8fe7fa78..ce078c59e0 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -490,7 +490,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return if self._connection_state_before_timeout and reply.error() == QNetworkReply.NoError: # There was a timeout, but we got a correct answer again. - Logger.log("d", "We got a response from the server after %0.1f of silence", time() - self._last_response_time) + Logger.log("d", "We got a response from the server after %0.1f of silence. Going back to previous state %s", time() - self._last_response_time, self._connection_state_before_timeout) self.setConnectionState(self._connection_state_before_timeout) self._connection_state_before_timeout = None @@ -499,7 +499,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if not status_code: - Logger.log("d", "A reply without a status code was received. Dropping the message") + Logger.log("d", "A reply from %s did not have status code.", reply.url().toString()) # Received no or empty reply return reply_url = reply.url().toString() From f60871e0684cb15b6343cdb30df0d1286c2a1401 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Aug 2016 13:07:20 +0200 Subject: [PATCH 168/297] Both timeout cases now abort the upload CURA-1851 --- NetworkPrinterOutputDevice.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index ce078c59e0..b98d67bbae 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -234,6 +234,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_state_before_timeout = self._connection_state self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your network-connections.")) self._connection_message.show() + # Check if we were uploading something. Abort if this is the case. + # Some operating systems handle this themselves, others give weird issues. + if self._post_reply: + self._post_reply.abort() + self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + self._progress_message.hide() self.setConnectionState(ConnectionState.error) if self._authentication_state == AuthState.NotAuthenticated: @@ -486,6 +492,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Some operating systems handle this themselves, others give weird issues. if self._post_reply: self._post_reply.abort() + self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + self._progress_message.hide() + self.setConnectionState(ConnectionState.error) return From 1dbd789d108721b66aee47ac5ad2b30b6bd76b50 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 17 Aug 2016 13:09:04 +0200 Subject: [PATCH 169/297] Only log messages without status when we are not in error mode. This prevents spamming the logs with messages --- NetworkPrinterOutputDevice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index b98d67bbae..7b3c4760b7 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -508,7 +508,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if not status_code: - Logger.log("d", "A reply from %s did not have status code.", reply.url().toString()) + if self._connection_state != ConnectionState.error: + Logger.log("d", "A reply from %s did not have status code.", reply.url().toString()) # Received no or empty reply return reply_url = reply.url().toString() From 99892ffd58f33a7da218157c1928d27073953f18 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 18 Aug 2016 13:23:29 +0200 Subject: [PATCH 170/297] Added exception handling for disconnect issues on mac CURA-1851 --- NetworkPrinterOutputDevice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 7b3c4760b7..c8ec7bd299 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -238,9 +238,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Some operating systems handle this themselves, others give weird issues. if self._post_reply: self._post_reply.abort() - self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + try: + self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + except TypeError: + pass # The disconnection can fail on mac in some cases. Ignore that. self._progress_message.hide() self.setConnectionState(ConnectionState.error) + return if self._authentication_state == AuthState.NotAuthenticated: self._verifyAuthentication() # We don't know if we are authenticated; check if we have correct auth. From edabe9d593ebc09cf263bc6e591f8500f33c711a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 19 Aug 2016 13:13:22 +0200 Subject: [PATCH 171/297] Improved logging a bit more CURA-1851 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index c8ec7bd299..f2a16bc5b9 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -503,7 +503,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return if self._connection_state_before_timeout and reply.error() == QNetworkReply.NoError: # There was a timeout, but we got a correct answer again. - Logger.log("d", "We got a response from the server after %0.1f of silence. Going back to previous state %s", time() - self._last_response_time, self._connection_state_before_timeout) + Logger.log("d", "We got a response (%s) from the server after %0.1f of silence. Going back to previous state %s", reply.url().toString(), time() - self._last_response_time, self._connection_state_before_timeout) self.setConnectionState(self._connection_state_before_timeout) self._connection_state_before_timeout = None From 5c2ac558eae92f08f58881ff695c078eb0c11c7a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 19 Aug 2016 13:14:10 +0200 Subject: [PATCH 172/297] Increased timeout time to 10 seconds CURA-1851 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f2a16bc5b9..99ab821d5e 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -137,7 +137,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_state_before_timeout = None self._last_response_time = time() - self._response_timeout_time = 5 + self._response_timeout_time = 10 self._not_authenticated_message = None self._last_command = "" From 1d6732273ca934fd035e3c2302681c7260b3b902 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 19 Aug 2016 13:32:17 +0200 Subject: [PATCH 173/297] Added local network connectivity detection CURA-1851 --- NetworkPrinterOutputDevice.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 99ab821d5e..c40f7a8df7 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -180,6 +180,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return self._address def _update_camera(self): + if not self._manager.networkAccessible(): + return ## Request new image url = QUrl("http://" + self._address + ":8080/?action=snapshot") image_request = QNetworkRequest(url) @@ -226,13 +228,24 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Request data from the connected device. def _update(self): + # Check if we have an connection in the first place. + if not self._manager.networkAccessible(): + if not self._connection_state_before_timeout: + Logger.log("d", "The network connection seems to be disabled. Going into timeout mode") + self._connection_state_before_timeout = self._connection_state + self.setConnectionState(ConnectionState.error) + self._connection_message = Message(i18n_catalog.i18nc("@info:status", + "The connection with the network was lost.")) + self._connection_message.show() + return + # Check that we aren't in a timeout state if self._last_response_time and not self._connection_state_before_timeout: if time() - self._last_response_time > self._response_timeout_time: # Go into timeout state. Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accessible.", time() - self._last_response_time) self._connection_state_before_timeout = self._connection_state - self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your network-connections.")) + self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your printer to see if it is connected.")) self._connection_message.show() # Check if we were uploading something. Abort if this is the case. # Some operating systems handle this themselves, others give weird issues. From 42bbf2b4c840cf63fde3de520b359b33ac04c65a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 19 Aug 2016 15:26:37 +0200 Subject: [PATCH 174/297] ALso abort if the network is not accesible CURA-1851 --- NetworkPrinterOutputDevice.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index c40f7a8df7..6063ab6fee 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -237,6 +237,15 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the network was lost.")) self._connection_message.show() + # Check if we were uploading something. Abort if this is the case. + # Some operating systems handle this themselves, others give weird issues. + if self._post_reply: + self._post_reply.abort() + try: + self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + except TypeError: + pass # The disconnection can fail on mac in some cases. Ignore that. + self._progress_message.hide() return # Check that we aren't in a timeout state From 0a0870792237f9425a66017fc1219f66b995f197 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 19 Aug 2016 15:30:34 +0200 Subject: [PATCH 175/297] Added logging about network accessible state CURA-1851 --- NetworkPrinterOutputDevice.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 6063ab6fee..f2f771122f 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -88,6 +88,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._manager = QNetworkAccessManager() self._manager.finished.connect(self._onFinished) self._manager.authenticationRequired.connect(self._onAuthenticationRequired) + self._manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) # for debug purposes self._post_request = None self._post_reply = None @@ -142,6 +143,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._last_command = "" + def _onNetworkAccesibleChanged(self, accessible): + Logger.log("d", "Network accessible state changed to: %s", accessible) + def _onAuthenticationTimer(self): self._authentication_counter += 1 self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100) From 5825793f28d16464dce88d421ea418891c20a7da Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 Aug 2016 09:49:57 +0200 Subject: [PATCH 176/297] QNetworkManager is now re-created every time we try to connect CURA-1851 --- NetworkPrinterOutputDevice.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f2f771122f..9cdab52881 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -83,12 +83,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network")) self.setIconName("print") - # QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly - # hook itself into the event loop, which results in events never being fired / done. - self._manager = QNetworkAccessManager() - self._manager.finished.connect(self._onFinished) - self._manager.authenticationRequired.connect(self._onAuthenticationRequired) - self._manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) # for debug purposes + self._manager = None self._post_request = None self._post_reply = None @@ -380,6 +375,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Start requesting data from printer def connect(self): self.close() # Ensure that previous connection (if any) is killed. + + if self._manager: + self._manager.finished.disconnect(self._onFinished) + self._manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged) + self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired) + + self._manager = QNetworkAccessManager() + self._manager.finished.connect(self._onFinished) + self._manager.authenticationRequired.connect(self._onAuthenticationRequired) + self._manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) # for debug purposes + self.setConnectionState(ConnectionState.connecting) self._update() # Manually trigger the first update, as we don't want to wait a few secs before it starts. self._update_camera() From 0c931424f96327a0affc5084e71e34036bc43b27 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 Aug 2016 14:08:01 +0200 Subject: [PATCH 177/297] Re-create the network manager every 30 seconds of not being connected CURA-1851 --- NetworkPrinterOutputDevice.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 9cdab52881..5347eb0e01 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -134,6 +134,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._last_response_time = time() self._response_timeout_time = 10 + self._recreate_network_manager_time = 30 # If we have no connection, re-create network manager every 30 sec. + self._recreate_network_manager_count = 1 self._not_authenticated_message = None self._last_command = "" @@ -227,6 +229,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Request data from the connected device. def _update(self): + # Connection is in timeout, check if we need to re-start the connection. + # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows. + # Re-creating the QNetworkManager seems to fix this issue. + if self._last_response_time and self._connection_state_before_timeout: + if time() - self._last_response_time > self._recreate_network_manager_time * self._recreate_network_manager_count: + self._recreate_network_manager_count += 1 + Logger.log("d", "Timeout lasted over 30 seconds, re-checking connection.") + self._createNetworkManager() + return + # Check if we have an connection in the first place. if not self._manager.networkAccessible(): if not self._connection_state_before_timeout: @@ -246,6 +258,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass # The disconnection can fail on mac in some cases. Ignore that. self._progress_message.hide() return + else: + self._recreate_network_manager_count = 1 # Check that we aren't in a timeout state if self._last_response_time and not self._connection_state_before_timeout: @@ -282,6 +296,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): print_job_request = QNetworkRequest(url) self._manager.get(print_job_request) + def _createNetworkManager(self): + if self._manager: + self._manager.finished.disconnect(self._onFinished) + self._manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged) + self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired) + + self._manager = QNetworkAccessManager() + self._manager.finished.connect(self._onFinished) + self._manager.authenticationRequired.connect(self._onAuthenticationRequired) + self._manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) # for debug purposes + ## Convenience function that gets information from the received json data and converts it to the right internal # values / variables def _spliceJSONData(self): @@ -376,15 +401,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def connect(self): self.close() # Ensure that previous connection (if any) is killed. - if self._manager: - self._manager.finished.disconnect(self._onFinished) - self._manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged) - self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired) - - self._manager = QNetworkAccessManager() - self._manager.finished.connect(self._onFinished) - self._manager.authenticationRequired.connect(self._onAuthenticationRequired) - self._manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) # for debug purposes + self._createNetworkManager() self.setConnectionState(ConnectionState.connecting) self._update() # Manually trigger the first update, as we don't want to wait a few secs before it starts. From 3a48fc23d1bd9da4bd2d5534154de9c0a918fcea Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Aug 2016 13:56:50 +0200 Subject: [PATCH 178/297] Added logging for the time it took to upload a print --- NetworkPrinterOutputDevice.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 5347eb0e01..0d99e26583 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -137,6 +137,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._recreate_network_manager_time = 30 # If we have no connection, re-create network manager every 30 sec. self._recreate_network_manager_count = 1 self._not_authenticated_message = None + self._send_gcode_start = time() # Time when the sending of the g-code started. self._last_command = "" @@ -456,6 +457,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # being in progress. def startPrint(self): try: + self._send_gcode_start = time() self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) self._progress_message.show() @@ -546,6 +548,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._post_reply: self._post_reply.abort() self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + Logger.log("d", "Uploading of print failed after %s", time() - self._send_gcode_start) self._progress_message.hide() self.setConnectionState(ConnectionState.error) @@ -681,6 +684,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): del self._material_post_objects[id(reply)] elif "print_job" in reply_url: reply.uploadProgress.disconnect(self._onUploadProgress) + Logger.log("d", "Uploading of print succeeded after %s", time() - self._send_gcode_start) self._progress_message.hide() elif reply.operation() == QNetworkAccessManager.PutOperation: From fa8eba78735ddb7fba3173f458356291dbfb0e9e Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 1 Sep 2016 15:14:53 +0200 Subject: [PATCH 179/297] Update wording --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 0d99e26583..a1708f395e 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -709,9 +709,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def materialHotendChangedMessage(self, callback): Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Changes on the Printer"), i18n_catalog.i18nc("@label", - "Do you want to change the PrintCores and materials to match your printer?"), + "Do you want to change the PrintCores and materials in Cura to match your printer?"), i18n_catalog.i18nc("@label", - "The PrintCores and/or materials on your printer were changed. For best results always slice for the PrintCores and materials that are inserted in your printer."), + "The PrintCores and/or materials on your printer were changed. For the best result, always slice for the PrintCores and materials that are inserted in your printer."), buttons=QMessageBox.Yes + QMessageBox.No, icon=QMessageBox.Question, callback=callback From cd9e049895e0657e9942ac8fedb955da27570637 Mon Sep 17 00:00:00 2001 From: Thomas Karl Pietrowski Date: Fri, 2 Sep 2016 11:12:28 +0200 Subject: [PATCH 180/297] CMake: Skip looking for C/C++ compiler --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad48443607..618e8ecbbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -project(JediWifiPrintingPlugin) +project(JediWifiPrintingPlugin NONE) cmake_minimum_required(VERSION 2.8.12) install(FILES From 6770db9c51bc98020698701e218f0479aafb532e Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 5 Sep 2016 13:45:57 +0200 Subject: [PATCH 181/297] Update wording when unable to start a new printjob UM3IC-193 --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index a1708f395e..a5e86483d5 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -363,12 +363,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def requestWrite(self, node, file_name = None, filter_by_machine = False): if self._progress != 0: - self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) + self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to start a new print job because the printer is busy. Please check the printer.")) self._error_message.show() return if self._json_printer_state["status"] != "idle": self._error_message = Message( - i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is not idle. Current printer status is %s.") % self._json_printer_state["status"]) + i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is busy. Current printer status is %s.") % self._json_printer_state["status"]) self._error_message.show() return elif self._authentication_state != AuthState.Authenticated: From ef667c7ec7bc094fa850612ed25ec68fe000df97 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 6 Sep 2016 14:44:53 +0200 Subject: [PATCH 182/297] If there is no material or correct cartridges inserted into printer, the job is refused CURA-2285 --- NetworkPrinterOutputDevice.py | 38 ++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index a5e86483d5..9af4c69ec2 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -381,12 +381,40 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._print_finished = True self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") - # TODO: Implement all checks. - # Check if cartridges are loaded at all (Error) - #self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"] != "" + print_information = Application.getInstance().getPrintInformation() - # Check if there is material loaded at all (Error) - #self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"] != "" + # TODO: Implement all checks. + # Check if PrintCores / materials are loaded at all (Error) + if print_information.materialLengths[0] != 0: # We need to print with extruder slot 1 + if self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"] == "": + Logger.log("e", "No cartridge loaded in slot 1, unable to start print") + self._error_message = Message( + i18n_catalog.i18nc("@info:status", "Unable to start a new print job, no PowerCore loaded in slot 1")) + self._error_message.show() + return + if self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"] == "": + Logger.log("e", "No material loaded in slot 1, unable to start print") + self._error_message = Message( + i18n_catalog.i18nc("@info:status", + "Unable to start a new print job, no material loaded in slot 1")) + self._error_message.show() + return + + if print_information.materialLengths[1] != 0: # We need to print with extruder slot 2 + if self._json_printer_state["heads"][0]["extruders"][1]["hotend"]["id"] == "": + Logger.log("e", "No cartridge loaded in slot 2, unable to start print") + self._error_message = Message( + i18n_catalog.i18nc("@info:status", + "Unable to start a new print job, no PowerCore loaded in slot 2")) + self._error_message.show() + return + if self._json_printer_state["heads"][0]["extruders"][1]["active_material"]["GUID"] == "": + Logger.log("e", "No material loaded in slot 2, unable to start print") + self._error_message = Message( + i18n_catalog.i18nc("@info:status", + "Unable to start a new print job, no material loaded in slot 2")) + self._error_message.show() + return # Check if there is enough material (Warning) #self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["length_remaining"] From d76927ef5d441c78feba5899ecc7884f6f9239ed Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 09:18:52 +0200 Subject: [PATCH 183/297] Added warning messages for mismatch / not enough material CURA-2285 --- NetworkPrinterOutputDevice.py | 77 ++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 9af4c69ec2..76f1ca097e 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -8,6 +8,7 @@ from UM.Message import Message import UM.Settings from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState +import cura.Settings.ExtruderManager from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot @@ -383,8 +384,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): print_information = Application.getInstance().getPrintInformation() - # TODO: Implement all checks. - # Check if PrintCores / materials are loaded at all (Error) + # Check if PrintCores / materials are loaded at all. Any failure in these results in an Error. if print_information.materialLengths[0] != 0: # We need to print with extruder slot 1 if self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"] == "": Logger.log("e", "No cartridge loaded in slot 1, unable to start print") @@ -416,10 +416,71 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._error_message.show() return - # Check if there is enough material (Warning) - #self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["length_remaining"] + warnings = [] # There might be multiple things wrong. Keep a list of all the stuff we need to warn about. - #TODO: Check if the cartridge is the right ID (give warning otherwise) + # Check if there is enough material. Any failure in these results in a warning. + material_length_1 = self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["length_remaining"] + if material_length_1 != -1 and print_information.materialLengths[0] > material_length_1: + warnings.append("not_enough_material_1") + + material_length_2 = self._json_printer_state["heads"][0]["extruders"][1]["active_material"]["length_remaining"] + if material_length_2 != -1 and print_information.materialLengths[1] > material_length_2: + warnings.append("not_enough_material_2") + + # Check if the right cartridges are loaded. Any failure in these results in a warning. + extruder_manager = cura.Settings.ExtruderManager.getInstance() + if print_information.materialLengths[0] != 0: + variant = extruder_manager.getExtruderStack(0).findContainer({"type": "variant"}) + if variant: + if variant.getId() != self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"]: + warnings.append("hotend_1") + + material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) + if material: + if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"]: + warnings.append("wrong_material_1") + + if print_information.materialLengths[1] != 0: + variant = extruder_manager.getExtruderStack(1).findContainer({"type": "variant"}) + if variant: + if variant.getId() != self._json_printer_state["heads"][0]["extruders"][1]["hotend"]["id"]: + warnings.append("hotend_2") + + material = extruder_manager.getExtruderStack(1).findContainer({"type": "material"}) + if material: + if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][1]["active_material"]["GUID"]: + warnings.append("wrong_material_2") + + if warnings: + text = i18n_catalog.i18nc("@label", "A number of configurations are mismatched. Are you sure you wish to print with the selected configuration?") + detailed_text = "
    " + if "not_enough_material_1" in warnings: + detailed_text += "
  • " + i18n_catalog.i18nc("@label", "Not enough material for spool 1.") + "
  • " + if "not_enough_material_2" in warnings: + detailed_text += "
  • " + i18n_catalog.i18nc("@label", "Not enough material for spool 2.") + "
  • " + if "hotend_1" in warnings: + detailed_text += "
  • " + i18n_catalog.i18nc("@label", + "Different PrintCore selected for extruder 1") + "
  • " + if "hotend_2" in warnings: + detailed_text += "
  • " + i18n_catalog.i18nc("@label", + "Different PrintCore selected for extruder 2") + "
  • " + if "wrong_material_1" in warnings: + detailed_text += "
  • " + i18n_catalog.i18nc("@label", + "Different material selected for extruder 1") + "
  • " + if "wrong_material_2" in warnings: + detailed_text += "
  • " + i18n_catalog.i18nc("@label", + "Different material selected for extruder 1") + "
  • " + + detailed_text += "
" + Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"), + text, + detailed_text, + buttons=QMessageBox.Yes + QMessageBox.No, + icon=QMessageBox.Question, + callback=self._configurationCallback + ) + + return self.startPrint() @@ -743,4 +804,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): buttons=QMessageBox.Yes + QMessageBox.No, icon=QMessageBox.Question, callback=callback - ) \ No newline at end of file + ) + + def _configurationCallback(self, button): + if button == QMessageBox.Yes: + self.startPrint() From b5d0b32bce66f6ab84e96cfb2e31fb034445801a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 09:48:18 +0200 Subject: [PATCH 184/297] Variant is now matched by name instead of ID CURA-2285 --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 76f1ca097e..f0b6d447a5 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -432,7 +432,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if print_information.materialLengths[0] != 0: variant = extruder_manager.getExtruderStack(0).findContainer({"type": "variant"}) if variant: - if variant.getId() != self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"]: + if variant.getName() != self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"]: warnings.append("hotend_1") material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) @@ -443,7 +443,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if print_information.materialLengths[1] != 0: variant = extruder_manager.getExtruderStack(1).findContainer({"type": "variant"}) if variant: - if variant.getId() != self._json_printer_state["heads"][0]["extruders"][1]["hotend"]["id"]: + if variant.getName() != self._json_printer_state["heads"][0]["extruders"][1]["hotend"]["id"]: warnings.append("hotend_2") material = extruder_manager.getExtruderStack(1).findContainer({"type": "material"}) From f2e93bfd978df91fbcccce293130b5bb378e5e11 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 11:46:55 +0200 Subject: [PATCH 185/297] Added time spent in timeout to re-check connection log CURA-2295 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f0b6d447a5..8ec071cb82 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -237,7 +237,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._last_response_time and self._connection_state_before_timeout: if time() - self._last_response_time > self._recreate_network_manager_time * self._recreate_network_manager_count: self._recreate_network_manager_count += 1 - Logger.log("d", "Timeout lasted over 30 seconds, re-checking connection.") + Logger.log("d", "Timeout lasted over 30 seconds (%.1fs), re-checking connection.", (time() - self._last_response_time)) self._createNetworkManager() return From 93cc25b4081e4a92feff6dc6bbeab2519e2f3560 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 11:56:19 +0200 Subject: [PATCH 186/297] Added exception handling for when wrapped reply object is already deleted CURA-2295 --- NetworkPrinterOutputDevice.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 8ec071cb82..6ab697ed5e 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -252,13 +252,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_message.show() # Check if we were uploading something. Abort if this is the case. # Some operating systems handle this themselves, others give weird issues. - if self._post_reply: - self._post_reply.abort() - try: - self._post_reply.uploadProgress.disconnect(self._onUploadProgress) - except TypeError: - pass # The disconnection can fail on mac in some cases. Ignore that. - self._progress_message.hide() + try: + if self._post_reply: + self._post_reply.abort() + try: + self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + except TypeError: + pass # The disconnection can fail on mac in some cases. Ignore that. + self._progress_message.hide() + except RuntimeError: + self._post_reply = None # It can happen that the wrapped c++ object is already deleted. return else: self._recreate_network_manager_count = 1 @@ -273,13 +276,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_message.show() # Check if we were uploading something. Abort if this is the case. # Some operating systems handle this themselves, others give weird issues. - if self._post_reply: - self._post_reply.abort() - try: - self._post_reply.uploadProgress.disconnect(self._onUploadProgress) - except TypeError: - pass # The disconnection can fail on mac in some cases. Ignore that. - self._progress_message.hide() + try: + if self._post_reply: + self._post_reply.abort() + try: + self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + except TypeError: + pass # The disconnection can fail on mac in some cases. Ignore that. + self._progress_message.hide() + except RuntimeError: + self._post_reply = None # It can happen that the wrapped c++ object is already deleted. self.setConnectionState(ConnectionState.error) return From d0823fda118dc8e7ff18bb4349ddf905db1fba0f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 12:58:48 +0200 Subject: [PATCH 187/297] If a timeout lasted to long without update, we only re-create network interface once CURA-2295 --- NetworkPrinterOutputDevice.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 6ab697ed5e..6f41f42e15 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -231,13 +231,21 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Request data from the connected device. def _update(self): + if self._last_response_time: + time_since_last_response = time() - self._last_response_time + else: + time_since_last_response = 0 + # Connection is in timeout, check if we need to re-start the connection. # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows. # Re-creating the QNetworkManager seems to fix this issue. if self._last_response_time and self._connection_state_before_timeout: - if time() - self._last_response_time > self._recreate_network_manager_time * self._recreate_network_manager_count: - self._recreate_network_manager_count += 1 - Logger.log("d", "Timeout lasted over 30 seconds (%.1fs), re-checking connection.", (time() - self._last_response_time)) + if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count: + # It can happen that we had a very long timeout (multiple times the recreate time). + # In that case we should jump through the point that the next update won't be right away. + while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time: + self._recreate_network_manager_count += 1 + Logger.log("d", "Timeout lasted over 30 seconds (%.1fs), re-checking connection.", time_since_last_response) self._createNetworkManager() return @@ -268,9 +276,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Check that we aren't in a timeout state if self._last_response_time and not self._connection_state_before_timeout: - if time() - self._last_response_time > self._response_timeout_time: + if time_since_last_response > self._response_timeout_time: # Go into timeout state. - Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accessible.", time() - self._last_response_time) + Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accessible.", time_since_last_response) self._connection_state_before_timeout = self._connection_state self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your printer to see if it is connected.")) self._connection_message.show() From 357f1186da295535e54902bd68e286c1b271d346 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 13:05:11 +0200 Subject: [PATCH 188/297] Added logging to start of uploading CURA-2295 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 6f41f42e15..07c22268c0 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -563,7 +563,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._send_gcode_start = time() self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) self._progress_message.show() - + Logger.log("d", "Started sending g-code to remote printer.") ## Mash the data into single string single_string_file_data = "" for line in self._gcode: From 97892273fc65e59fb4246d68fc3010f594192648 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 14:04:23 +0200 Subject: [PATCH 189/297] Updated logging --- NetworkPrinterOutputDevice.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 07c22268c0..eade037315 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -155,6 +155,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _onAuthenticationRequired(self, reply, authenticator): if self._authentication_id is not None and self._authentication_key is not None: + Logger.log("d", "Authentication was required. Setting up authenticator.") authenticator.setUser(self._authentication_id) authenticator.setPassword(self._authentication_key) @@ -194,11 +195,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # \param auth_state \type{AuthState} Enum value representing the new auth state def setAuthenticationState(self, auth_state): if auth_state == AuthState.AuthenticationRequested: + Logger.log("d", "Authentication state changed to authentication requested.") self.setAcceptsCommands(False) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0} without access to control the printer.").format(self.name)) self._authentication_requested_message.show() self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: + Logger.log("d", "Authentication state changed to authenticated") self.setAcceptsCommands(True) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}.").format(self.name)) self._authentication_requested_message.hide() @@ -211,6 +214,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Once we are authenticated we need to send all material profiles. self.sendMaterialProfiles() elif auth_state == AuthState.AuthenticationDenied: + Logger.log("d", "Authentication state changed to authentication denied") self.setAcceptsCommands(False) self._authentication_requested_message.hide() self._authentication_failed_message.show() @@ -606,6 +610,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): ## Check if the authentication request was allowed by the printer. def _checkAuthentication(self): + Logger.log("d", "Checking if authentication is correct.") self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id)))) ## Request a authentication key from the printer so we can be authenticated @@ -801,6 +806,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _onUploadProgress(self, bytes_sent, bytes_total): if bytes_total > 0: new_progress = bytes_sent / bytes_total * 100 + # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get + # timeout responses if this happens. + self._last_response_time = time() if new_progress > self._progress_message.getProgress(): self._progress_message.show() # Ensure that the message is visible. self._progress_message.setProgress(bytes_sent / bytes_total * 100) From 1f3c8e093948aeb763ed82b636c650f8433894f2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 14:25:24 +0200 Subject: [PATCH 190/297] If saved authentication is wrong, it's now correctly reset CURA-2295 --- NetworkPrinterOutputDevice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index eade037315..c77ac95a3f 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -232,6 +232,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_requested_message.setProgress(0) self._authentication_id = None self._authentication_key = None + self._createNetworkManager() # Re-create network manager to force re-authentication. ## Request data from the connected device. def _update(self): @@ -394,6 +395,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", "Not authenticated to print with this machine. Unable to start a new job.")) self._not_authenticated_message.show() + Logger.log("d", "Attempting to perform an action without authentication. Auth state is %s", self._authentication_state) return Application.getInstance().showPrintMonitor.emit(True) @@ -747,7 +749,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("i", "Not authenticated. Attempting to request authentication") self._requestAuthentication() elif status_code == 403: - pass + # If we already had an auth (eg; didn't request one), we only need a single 403 to see it as denied. + if self._authentication_state != AuthState.AuthenticationRequested: + self.setAuthenticationState(AuthState.AuthenticationDenied) elif status_code == 200: self.setAuthenticationState(AuthState.Authenticated) global_container_stack = Application.getInstance().getGlobalContainerStack() From 65a2cedf9c8202524b733ad3f2f0493e51bd626e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 15:05:07 +0200 Subject: [PATCH 191/297] Cleaned up code duplication in warning system CURA-2285 --- NetworkPrinterOutputDevice.py | 105 ++++++++++------------------------ 1 file changed, 31 insertions(+), 74 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index c77ac95a3f..de78a03467 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -405,91 +405,48 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): print_information = Application.getInstance().getPrintInformation() # Check if PrintCores / materials are loaded at all. Any failure in these results in an Error. - if print_information.materialLengths[0] != 0: # We need to print with extruder slot 1 - if self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"] == "": - Logger.log("e", "No cartridge loaded in slot 1, unable to start print") - self._error_message = Message( - i18n_catalog.i18nc("@info:status", "Unable to start a new print job, no PowerCore loaded in slot 1")) - self._error_message.show() - return - if self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"] == "": - Logger.log("e", "No material loaded in slot 1, unable to start print") - self._error_message = Message( - i18n_catalog.i18nc("@info:status", - "Unable to start a new print job, no material loaded in slot 1")) - self._error_message.show() - return - - if print_information.materialLengths[1] != 0: # We need to print with extruder slot 2 - if self._json_printer_state["heads"][0]["extruders"][1]["hotend"]["id"] == "": - Logger.log("e", "No cartridge loaded in slot 2, unable to start print") - self._error_message = Message( - i18n_catalog.i18nc("@info:status", - "Unable to start a new print job, no PowerCore loaded in slot 2")) - self._error_message.show() - return - if self._json_printer_state["heads"][0]["extruders"][1]["active_material"]["GUID"] == "": - Logger.log("e", "No material loaded in slot 2, unable to start print") - self._error_message = Message( - i18n_catalog.i18nc("@info:status", - "Unable to start a new print job, no material loaded in slot 2")) - self._error_message.show() - return + for index in range(0, self._num_extruders): + if print_information.materialLengths[index] != 0: + if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "": + Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1) + self._error_message = Message( + i18n_catalog.i18nc("@info:status", "Unable to start a new print job; no PrinterCore loaded in slot {0}".format(index + 1))) + self._error_message.show() + return + if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"] == "": + Logger.log("e", "No material loaded in slot %s, unable to start print", index + 1) + self._error_message = Message( + i18n_catalog.i18nc("@info:status", + "Unable to start a new print job; no material loaded in slot {0}".format(index + 1))) + self._error_message.show() + return warnings = [] # There might be multiple things wrong. Keep a list of all the stuff we need to warn about. - # Check if there is enough material. Any failure in these results in a warning. - material_length_1 = self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["length_remaining"] - if material_length_1 != -1 and print_information.materialLengths[0] > material_length_1: - warnings.append("not_enough_material_1") + for index in range(0, self._num_extruders): + # Check if there is enough material. Any failure in these results in a warning. + material_length = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["length_remaining"] + if material_length != -1 and print_information.materialLengths[index] > material_length: + warnings.append(i18n_catalog.i18nc("@label", "Not enough material for spool {0}.").format(index+1)) - material_length_2 = self._json_printer_state["heads"][0]["extruders"][1]["active_material"]["length_remaining"] - if material_length_2 != -1 and print_information.materialLengths[1] > material_length_2: - warnings.append("not_enough_material_2") - - # Check if the right cartridges are loaded. Any failure in these results in a warning. - extruder_manager = cura.Settings.ExtruderManager.getInstance() - if print_information.materialLengths[0] != 0: - variant = extruder_manager.getExtruderStack(0).findContainer({"type": "variant"}) - if variant: - if variant.getName() != self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"]: - warnings.append("hotend_1") + # Check if the right cartridges are loaded. Any failure in these results in a warning. + extruder_manager = cura.Settings.ExtruderManager.getInstance() + if print_information.materialLengths[index] != 0: + variant = extruder_manager.getExtruderStack(0).findContainer({"type": "variant"}) + if variant: + if variant.getName() != self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]: + warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore selected for extruder {0}".format(index + 1))) material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) if material: - if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"]: - warnings.append("wrong_material_1") - - if print_information.materialLengths[1] != 0: - variant = extruder_manager.getExtruderStack(1).findContainer({"type": "variant"}) - if variant: - if variant.getName() != self._json_printer_state["heads"][0]["extruders"][1]["hotend"]["id"]: - warnings.append("hotend_2") - - material = extruder_manager.getExtruderStack(1).findContainer({"type": "material"}) - if material: - if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][1]["active_material"]["GUID"]: - warnings.append("wrong_material_2") + if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"]: + warnings.append(i18n_catalog.i18nc("@label", "Different material selected for extruder {0}").format(index + 1)) if warnings: text = i18n_catalog.i18nc("@label", "A number of configurations are mismatched. Are you sure you wish to print with the selected configuration?") detailed_text = "
    " - if "not_enough_material_1" in warnings: - detailed_text += "
  • " + i18n_catalog.i18nc("@label", "Not enough material for spool 1.") + "
  • " - if "not_enough_material_2" in warnings: - detailed_text += "
  • " + i18n_catalog.i18nc("@label", "Not enough material for spool 2.") + "
  • " - if "hotend_1" in warnings: - detailed_text += "
  • " + i18n_catalog.i18nc("@label", - "Different PrintCore selected for extruder 1") + "
  • " - if "hotend_2" in warnings: - detailed_text += "
  • " + i18n_catalog.i18nc("@label", - "Different PrintCore selected for extruder 2") + "
  • " - if "wrong_material_1" in warnings: - detailed_text += "
  • " + i18n_catalog.i18nc("@label", - "Different material selected for extruder 1") + "
  • " - if "wrong_material_2" in warnings: - detailed_text += "
  • " + i18n_catalog.i18nc("@label", - "Different material selected for extruder 1") + "
  • " + for warning in warnings: + detailed_text += "
  • " + warning + "
  • " detailed_text += "
" Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"), From 9bd4d3c0a8d005f0aae0fc8086046347f2738acf Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 8 Sep 2016 11:26:01 +0200 Subject: [PATCH 192/297] Update configuration mismatch dialog layout and wording Also return to settings tab when not continuing with the print CURA-2285 --- NetworkPrinterOutputDevice.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index de78a03467..e6964d9715 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -440,27 +440,35 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) if material: if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"]: - warnings.append(i18n_catalog.i18nc("@label", "Different material selected for extruder {0}").format(index + 1)) + warnings.append(i18n_catalog.i18nc("@label", "Different material selected for extruder {0}").format(index + 1)) if warnings: - text = i18n_catalog.i18nc("@label", "A number of configurations are mismatched. Are you sure you wish to print with the selected configuration?") - detailed_text = "
    " + text = i18n_catalog.i18nc("@label", "Are you sure you wish to print with the selected configuration?") + informative_text = i18n_catalog.i18nc("@label", "There is a mismatch between the configuration of the printer and Cura. " + "For the best result, always slice for the PrintCores and materials that are inserted in your printer.") + detailed_text = "" for warning in warnings: - detailed_text += "
  • " + warning + "
  • " + detailed_text += warning + "\n" - detailed_text += "
" Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"), text, + informative_text, detailed_text, buttons=QMessageBox.Yes + QMessageBox.No, icon=QMessageBox.Question, - callback=self._configurationCallback + callback=self._configurationMismatchMessageCallback ) return self.startPrint() + def _configurationMismatchMessageCallback(self, button): + if button == QMessageBox.Yes: + self.startPrint() + else: + Application.getInstance().showPrintMonitor.emit(False) + def isConnected(self): return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error @@ -788,7 +796,3 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): icon=QMessageBox.Question, callback=callback ) - - def _configurationCallback(self, button): - if button == QMessageBox.Yes: - self.startPrint() From 28bfc364725c8c5feefc42d1d9693b25503c30ee Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 8 Sep 2016 16:37:10 +0200 Subject: [PATCH 193/297] Recreate network manager count is now always increased once --- NetworkPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index de78a03467..b119f5ff8b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -246,6 +246,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Re-creating the QNetworkManager seems to fix this issue. if self._last_response_time and self._connection_state_before_timeout: if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count: + self._recreate_network_manager_count += 1 # It can happen that we had a very long timeout (multiple times the recreate time). # In that case we should jump through the point that the next update won't be right away. while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time: From d0823ebff04ac42679433ac87cb5d4ff51f39aca Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 8 Sep 2016 16:57:11 +0200 Subject: [PATCH 194/297] Added logging when the post was aborted due to connection loss CURA-2295 --- NetworkPrinterOutputDevice.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 208ad80d51..7038c4e672 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -268,11 +268,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Some operating systems handle this themselves, others give weird issues. try: if self._post_reply: - self._post_reply.abort() + Logger.log("d", "Stopping post upload because the connection was lost.") try: self._post_reply.uploadProgress.disconnect(self._onUploadProgress) except TypeError: pass # The disconnection can fail on mac in some cases. Ignore that. + + self._post_reply.abort() self._progress_message.hide() except RuntimeError: self._post_reply = None # It can happen that the wrapped c++ object is already deleted. @@ -292,11 +294,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Some operating systems handle this themselves, others give weird issues. try: if self._post_reply: - self._post_reply.abort() + Logger.log("d", "Stopping post upload because the connection was lost.") try: self._post_reply.uploadProgress.disconnect(self._onUploadProgress) except TypeError: pass # The disconnection can fail on mac in some cases. Ignore that. + + self._post_reply.abort() self._progress_message.hide() except RuntimeError: self._post_reply = None # It can happen that the wrapped c++ object is already deleted. From ad496a646775917c2924aee628667b79ea3f0407 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 8 Sep 2016 17:27:18 +0200 Subject: [PATCH 195/297] Counter is now only reset if no previous state was set --- NetworkPrinterOutputDevice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 7038c4e672..5e16c0d5f7 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -280,7 +280,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._post_reply = None # It can happen that the wrapped c++ object is already deleted. return else: - self._recreate_network_manager_count = 1 + if not self._connection_state_before_timeout: + self._recreate_network_manager_count = 1 # Check that we aren't in a timeout state if self._last_response_time and not self._connection_state_before_timeout: From 3a4ccf13675ae4828ebd111a5493c1f2216c7ee2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 9 Sep 2016 10:25:12 +0200 Subject: [PATCH 196/297] Uploading g-codes is now done g-zipped CURA-2286 --- NetworkPrinterOutputDevice.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 5e16c0d5f7..f65ef30f9b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -17,6 +17,7 @@ from PyQt5.QtWidgets import QMessageBox import json import os +import gzip from time import time @@ -40,7 +41,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._properties = properties # Properties dict as provided by zero conf self._gcode = None - self._print_finished = True # _print_finsihed == False means we're halfway in a print + self._print_finished = True # _print_finsihed == False means we're halfway in a print + + self._use_gzip = True # Should we use g-zip compression before sending the data? # This holds the full JSON file that was received from the last request. # The JSON looks like: @@ -546,7 +549,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): for line in self._gcode: single_string_file_data += line - file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName + if self._use_gzip: + file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName + single_string_file_data = gzip.compress(single_string_file_data.encode("utf-8")) + else: + file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName + single_string_file_data = single_string_file_data.encode("utf-8") ## Create multi_part request self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) @@ -555,7 +563,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._post_part = QHttpPart() self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name) - self._post_part.setBody(single_string_file_data.encode()) + self._post_part.setBody(single_string_file_data) self._post_multi_part.append(self._post_part) url = QUrl("http://" + self._address + self._api_prefix + "print_job") From dc0124f502090a9a87e31db55ab48ed496e3fe73 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 9 Sep 2016 11:04:16 +0200 Subject: [PATCH 197/297] If serialization of XML fails, we don't try to send it to the printer --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f65ef30f9b..4fef337b13 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -607,7 +607,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material"): try: xml_data = container.serialize() - if xml_data == "": + if xml_data == "" or xml_data is None: continue material_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) From f75b4739f68837476e7e6725ca783589a23002d7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 8 Sep 2016 16:57:11 +0200 Subject: [PATCH 198/297] Added logging when the post was aborted due to connection loss CURA-2295 --- NetworkPrinterOutputDevice.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 208ad80d51..7038c4e672 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -268,11 +268,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Some operating systems handle this themselves, others give weird issues. try: if self._post_reply: - self._post_reply.abort() + Logger.log("d", "Stopping post upload because the connection was lost.") try: self._post_reply.uploadProgress.disconnect(self._onUploadProgress) except TypeError: pass # The disconnection can fail on mac in some cases. Ignore that. + + self._post_reply.abort() self._progress_message.hide() except RuntimeError: self._post_reply = None # It can happen that the wrapped c++ object is already deleted. @@ -292,11 +294,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Some operating systems handle this themselves, others give weird issues. try: if self._post_reply: - self._post_reply.abort() + Logger.log("d", "Stopping post upload because the connection was lost.") try: self._post_reply.uploadProgress.disconnect(self._onUploadProgress) except TypeError: pass # The disconnection can fail on mac in some cases. Ignore that. + + self._post_reply.abort() self._progress_message.hide() except RuntimeError: self._post_reply = None # It can happen that the wrapped c++ object is already deleted. From f5886e5ecc64eee4ff4b7d2827b2b1595f64a2ea Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 8 Sep 2016 17:27:18 +0200 Subject: [PATCH 199/297] Counter is now only reset if no previous state was set --- NetworkPrinterOutputDevice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 7038c4e672..5e16c0d5f7 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -280,7 +280,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._post_reply = None # It can happen that the wrapped c++ object is already deleted. return else: - self._recreate_network_manager_count = 1 + if not self._connection_state_before_timeout: + self._recreate_network_manager_count = 1 # Check that we aren't in a timeout state if self._last_response_time and not self._connection_state_before_timeout: From a6a47b0aa27c331b38134498955e83c1e5e88f06 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 9 Sep 2016 10:25:12 +0200 Subject: [PATCH 200/297] Uploading g-codes is now done g-zipped CURA-2286 --- NetworkPrinterOutputDevice.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 5e16c0d5f7..f65ef30f9b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -17,6 +17,7 @@ from PyQt5.QtWidgets import QMessageBox import json import os +import gzip from time import time @@ -40,7 +41,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._properties = properties # Properties dict as provided by zero conf self._gcode = None - self._print_finished = True # _print_finsihed == False means we're halfway in a print + self._print_finished = True # _print_finsihed == False means we're halfway in a print + + self._use_gzip = True # Should we use g-zip compression before sending the data? # This holds the full JSON file that was received from the last request. # The JSON looks like: @@ -546,7 +549,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): for line in self._gcode: single_string_file_data += line - file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName + if self._use_gzip: + file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName + single_string_file_data = gzip.compress(single_string_file_data.encode("utf-8")) + else: + file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName + single_string_file_data = single_string_file_data.encode("utf-8") ## Create multi_part request self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) @@ -555,7 +563,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._post_part = QHttpPart() self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name) - self._post_part.setBody(single_string_file_data.encode()) + self._post_part.setBody(single_string_file_data) self._post_multi_part.append(self._post_part) url = QUrl("http://" + self._address + self._api_prefix + "print_job") From 97cf79c2e18ce001da1a94a3620b493104e44ab0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 9 Sep 2016 11:04:16 +0200 Subject: [PATCH 201/297] If serialization of XML fails, we don't try to send it to the printer --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f65ef30f9b..4fef337b13 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -607,7 +607,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material"): try: xml_data = container.serialize() - if xml_data == "": + if xml_data == "" or xml_data is None: continue material_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) From 9de064fdba7cb5490b48979ad4b6b7f5743f0c07 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 9 Sep 2016 15:20:08 +0200 Subject: [PATCH 202/297] Fixed issue where large files could no longer be sent CURA-2286 --- NetworkPrinterOutputDevice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 4fef337b13..7bb1eb59d9 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -552,6 +552,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._use_gzip: file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName single_string_file_data = gzip.compress(single_string_file_data.encode("utf-8")) + # Pretend that this is a response, as zipping might take a bit of time. + self._last_response_time = time() else: file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName single_string_file_data = single_string_file_data.encode("utf-8") From 3747db0f4daf4759e4025907981baec691e687ce Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 9 Sep 2016 15:28:37 +0200 Subject: [PATCH 203/297] Added logging to see how long compression took CURA-2286 --- NetworkPrinterOutputDevice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 7bb1eb59d9..c606546e3e 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -551,7 +551,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._use_gzip: file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName + compress_time = time() single_string_file_data = gzip.compress(single_string_file_data.encode("utf-8")) + Logger.log("d", "It took %s seconds to compress the file", time() - compress_time) # Pretend that this is a response, as zipping might take a bit of time. self._last_response_time = time() else: From 7f0194cce01d1a50f01b4e95f05ce33d9a59403b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 9 Sep 2016 15:44:02 +0200 Subject: [PATCH 204/297] We now compress the g-code layer by layer so the GIL won't always lock fully CURA-2286 --- NetworkPrinterOutputDevice.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index c606546e3e..d5f74f5787 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -18,8 +18,10 @@ from PyQt5.QtWidgets import QMessageBox import json import os import gzip +import zlib from time import time +from time import sleep i18n_catalog = i18nCatalog("cura") @@ -544,21 +546,22 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) self._progress_message.show() Logger.log("d", "Started sending g-code to remote printer.") + ## Mash the data into single string - single_string_file_data = "" + byte_array_file_data = b"" for line in self._gcode: - single_string_file_data += line + if self._use_gzip: + byte_array_file_data += gzip.compress(line.encode("utf-8")) + sleep(0) # Yield. + # Pretend that this is a response, as zipping might take a bit of time. + self._last_response_time = time() + else: + byte_array_file_data += line.encode("utf-8") if self._use_gzip: file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName - compress_time = time() - single_string_file_data = gzip.compress(single_string_file_data.encode("utf-8")) - Logger.log("d", "It took %s seconds to compress the file", time() - compress_time) - # Pretend that this is a response, as zipping might take a bit of time. - self._last_response_time = time() else: file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName - single_string_file_data = single_string_file_data.encode("utf-8") ## Create multi_part request self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) @@ -567,7 +570,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._post_part = QHttpPart() self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name) - self._post_part.setBody(single_string_file_data) + self._post_part.setBody(byte_array_file_data) self._post_multi_part.append(self._post_part) url = QUrl("http://" + self._address + self._api_prefix + "print_job") From f294d580a98cf8837bb5770d60e8cd8a37aaef2c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 9 Sep 2016 16:46:04 +0200 Subject: [PATCH 205/297] Zipping the data no longer causes GUI to freeze CURA-2286 --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index d5f74f5787..0624c817d1 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -11,7 +11,7 @@ from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState import cura.Settings.ExtruderManager from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply -from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot +from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot, QCoreApplication from PyQt5.QtGui import QImage from PyQt5.QtWidgets import QMessageBox @@ -552,7 +552,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): for line in self._gcode: if self._use_gzip: byte_array_file_data += gzip.compress(line.encode("utf-8")) - sleep(0) # Yield. + QCoreApplication.processEvents() # Ensure that the GUI does not freeze. # Pretend that this is a response, as zipping might take a bit of time. self._last_response_time = time() else: From ae161748ef9294bb065ad6800bedb9851f134c56 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 12 Sep 2016 10:39:58 +0200 Subject: [PATCH 206/297] Material type is now only checked if we actually use that extruder CURA-2313 --- NetworkPrinterOutputDevice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 0624c817d1..ce0aba70af 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -448,10 +448,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if variant.getName() != self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]: warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore selected for extruder {0}".format(index + 1))) - material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) - if material: - if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"]: - warnings.append(i18n_catalog.i18nc("@label", "Different material selected for extruder {0}").format(index + 1)) + material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) + if material: + if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"]: + warnings.append(i18n_catalog.i18nc("@label", "Different material selected for extruder {0}").format(index + 1)) if warnings: text = i18n_catalog.i18nc("@label", "Are you sure you wish to print with the selected configuration?") From 820d91486f192c4785a7291d574def4e063a53d2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 12 Sep 2016 10:44:56 +0200 Subject: [PATCH 207/297] Printer setting mismatches are now also logged as warnings CURA-2313 --- NetworkPrinterOutputDevice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index ce0aba70af..850e893d96 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -438,6 +438,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Check if there is enough material. Any failure in these results in a warning. material_length = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["length_remaining"] if material_length != -1 and print_information.materialLengths[index] > material_length: + Logger.log("w", "Printer reports that there is not enough material left for extruder %s. We need %s and the printer has %s", index + 1, print_information.materialLengths[index], material_length) warnings.append(i18n_catalog.i18nc("@label", "Not enough material for spool {0}.").format(index+1)) # Check if the right cartridges are loaded. Any failure in these results in a warning. @@ -446,11 +447,15 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): variant = extruder_manager.getExtruderStack(0).findContainer({"type": "variant"}) if variant: if variant.getName() != self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]: + Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"], variant.getName()) warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore selected for extruder {0}".format(index + 1))) material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) if material: if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"]: + Logger.log("w", "Extruder %s has a different material (%s) as Cura (%s)", index + 1, + self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"], + material.getMetaDataEntry("GUID")) warnings.append(i18n_catalog.i18nc("@label", "Different material selected for extruder {0}").format(index + 1)) if warnings: @@ -469,7 +474,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): icon=QMessageBox.Question, callback=self._configurationMismatchMessageCallback ) - return self.startPrint() From feae612d5511a2a402619b05b501f10241372897 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 12 Sep 2016 10:54:57 +0200 Subject: [PATCH 208/297] Look at the correct stack for variant CURA-2313 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 850e893d96..627f0b7df3 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -444,7 +444,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Check if the right cartridges are loaded. Any failure in these results in a warning. extruder_manager = cura.Settings.ExtruderManager.getInstance() if print_information.materialLengths[index] != 0: - variant = extruder_manager.getExtruderStack(0).findContainer({"type": "variant"}) + variant = extruder_manager.getExtruderStack(index).findContainer({"type": "variant"}) if variant: if variant.getName() != self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]: Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"], variant.getName()) From f914ef5424308764f0e53c05fbb1fb64909a93bd Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 12 Sep 2016 11:59:25 +0200 Subject: [PATCH 209/297] We now save the auth data right away in order to prevent mismatches CURA-2279 --- NetworkPrinterOutputDevice.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 627f0b7df3..956d4bc2e9 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -755,6 +755,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): global_container_stack.setMetaDataEntry("network_authentication_id", self._authentication_id) else: global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id) + Application.getInstance().saveStack(global_container_stack) # Force save so we are sure the data is not lost. Logger.log("i", "Authentication succeeded") else: # Got a response that we didn't expect, so something went wrong. Logger.log("w", "While trying to authenticate, we got an unexpected response: %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)) @@ -775,6 +776,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if "/auth/request" in reply_url: # We got a response to requesting authentication. data = json.loads(bytes(reply.readAll()).decode("utf-8")) + + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: # Remove any old data. + global_container_stack.removeMetaDataEntry("network_authentication_key") + global_container_stack.removeMetaDataEntry("network_authentication_id") + Application.getInstance().saveStack(global_container_stack) # Force saving so we don't keep wrong auth data. + self._authentication_key = data["key"] self._authentication_id = data["id"] Logger.log("i", "Got a new authentication ID. Waiting for authorization: %s", self._authentication_id ) From 1d91827664e0857c0a031ba53db14c819eeb94a8 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 12 Sep 2016 13:31:07 +0200 Subject: [PATCH 210/297] NetworkPrinter now uses printerState to store global state CURA-2235 --- NetworkPrinterOutputDevice.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 956d4bc2e9..919df506d4 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -364,6 +364,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): head_y = self._json_printer_state["heads"][0]["position"]["y"] head_z = self._json_printer_state["heads"][0]["position"]["z"] self._updateHeadPosition(head_x, head_y, head_z) + self._updatePrinterState(self._json_printer_state["status"]) + def close(self): self._updateJobState("") @@ -397,9 +399,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to start a new print job because the printer is busy. Please check the printer.")) self._error_message.show() return - if self._json_printer_state["status"] != "idle": + if self._printer_state != "idle": self._error_message = Message( - i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is busy. Current printer status is %s.") % self._json_printer_state["status"]) + i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is busy. Current printer status is %s.") % self._printer_state) self._error_message.show() return elif self._authentication_state != AuthState.Authenticated: From e09de5257a8733765ee06d824dc3237a6c40201d Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 12 Sep 2016 13:36:39 +0200 Subject: [PATCH 211/297] Fix text about connection state in print monitor --- NetworkPrinterOutputDevice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 0624c817d1..032010b4e8 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -202,7 +202,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if auth_state == AuthState.AuthenticationRequested: Logger.log("d", "Authentication state changed to authentication requested.") self.setAcceptsCommands(False) - self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0} without access to control the printer.").format(self.name)) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}, waiting for access to control the printer.").format(self.name)) self._authentication_requested_message.show() self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: @@ -221,6 +221,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif auth_state == AuthState.AuthenticationDenied: Logger.log("d", "Authentication state changed to authentication denied") self.setAcceptsCommands(False) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}, no access to control the printer.").format(self.name)) self._authentication_requested_message.hide() self._authentication_failed_message.show() From 220abd2cdcb2cab672a53b69b415a7ff3ea95865 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 12 Sep 2016 13:49:00 +0200 Subject: [PATCH 212/297] Fix i18n context --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index b030fe61e6..6b7457ad36 100644 --- a/__init__.py +++ b/__init__.py @@ -10,7 +10,7 @@ def getMetaData(): "plugin": { "name": "UM3 Network Connection", "author": "Ultimaker", - "description": catalog.i18nc("Wifi connection", "UM3 Network Connection"), + "description": catalog.i18nc("@info:whatsthis", "Manages network connections to Ultimaker 3 printers"), "api": 3 } } From af4cfac6acf27376fa0599d801a890d8964c86b2 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 12 Sep 2016 14:19:34 +0200 Subject: [PATCH 213/297] Add a link to a network connection troubleshooting guide CURA-2035 --- DiscoverUM3Action.qml | 118 ++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 49 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index dcfd76792b..a33ddd31fe 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -77,70 +77,90 @@ Cura.MachineAction Row { + id: contentRow width: parent.width spacing: UM.Theme.getSize("default_margin").width - ScrollView + + Column { - id: objectListContainer - frameVisible: true width: parent.width * 0.5 - height: base.height - parent.y + spacing: UM.Theme.getSize("default_margin").height - Rectangle + ScrollView { - parent: viewport - anchors.fill: parent - color: palette.light - } - - ListView - { - id: listview - model: manager.foundDevices - onModelChanged: - { - var selectedKey = manager.getStoredKey(); - for(var i = 0; i < model.length; i++) { - if(model[i].getKey() == selectedKey) - { - currentIndex = i; - return - } - } - currentIndex = -1; - } + id: objectListContainer + frameVisible: true width: parent.width - currentIndex: -1 - onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex] - Component.onCompleted: manager.startDiscovery() - delegate: Rectangle - { - height: childrenRect.height - color: ListView.isCurrentItem ? palette.highlight : index % 2 ? palette.base : palette.alternateBase - width: parent.width - Label - { - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width - anchors.right: parent.right - text: listview.model[index].name - color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text - elide: Text.ElideRight - } + height: base.height - contentRow.y - discoveryTip.height - MouseArea + Rectangle + { + parent: viewport + anchors.fill: parent + color: palette.light + } + + ListView + { + id: listview + model: manager.foundDevices + onModelChanged: { - anchors.fill: parent; - onClicked: - { - if(!parent.ListView.isCurrentItem) + var selectedKey = manager.getStoredKey(); + for(var i = 0; i < model.length; i++) { + if(model[i].getKey() == selectedKey) { - parent.ListView.view.currentIndex = index; + currentIndex = i; + return + } + } + currentIndex = -1; + } + width: parent.width + currentIndex: -1 + onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex] + Component.onCompleted: manager.startDiscovery() + delegate: Rectangle + { + height: childrenRect.height + color: ListView.isCurrentItem ? palette.highlight : index % 2 ? palette.base : palette.alternateBase + width: parent.width + Label + { + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.right: parent.right + text: listview.model[index].name + color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text + elide: Text.ElideRight + } + + MouseArea + { + anchors.fill: parent; + onClicked: + { + if(!parent.ListView.isCurrentItem) + { + parent.ListView.view.currentIndex = index; + } } } } } } + Label + { + id: discoveryTip + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + //: Tips label + //TODO: get actual link from webteam + text: catalog.i18nc("@label", "If your Ultimaker 3 is not listed, read the Ultimaker 3 network troubleshooting guide").arg("https://ultimaker.com/en/troubleshooting"); + onLinkActivated: Qt.openUrlExternally(link) + } + } Column { From 0e0569a37c5d52b4ddf778cac45ec8f6bfef502b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 12 Sep 2016 15:40:43 +0200 Subject: [PATCH 214/297] Update user-facing messages for consistency Make sure we have a consistent terminology; requesting access vs pairing/authentication. --- NetworkPrinterOutputDevice.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index afc5b0e2e1..48a9554a50 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -127,11 +127,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_id = None self._authentication_key = None - self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Requested access. Please approve the request on the printer"), lifetime = 0, dismissable = False, progress = 0) - self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "Pairing request failed due to a timeout or the printer refused the request.")) - self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry "), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the authentication request")) + self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer requested. Please approve the request on the printer"), lifetime = 0, dismissable = False, progress = 0) + self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "")) + self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry "), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the access request")) self._authentication_failed_message.actionTriggered.connect(self.messageActionTriggered) - self._authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Printer was successfully paired with Cura")) + self._authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer accepted")) self._camera_image = QImage() @@ -202,7 +202,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if auth_state == AuthState.AuthenticationRequested: Logger.log("d", "Authentication state changed to authentication requested.") self.setAcceptsCommands(False) - self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}, waiting for access to control the printer.").format(self.name)) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}. Please approve the access request on the printer.").format(self.name)) self._authentication_requested_message.show() self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: @@ -221,8 +221,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif auth_state == AuthState.AuthenticationDenied: Logger.log("d", "Authentication state changed to authentication denied") self.setAcceptsCommands(False) - self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}, no access to control the printer.").format(self.name)) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}. No access to control the printer.").format(self.name)) self._authentication_requested_message.hide() + if self._authentication_timer.remainingTime() > 0: + self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request was denied on the printer.")) + else: + self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request failed due to a timeout.")) self._authentication_failed_message.show() # Stop waiting for a response @@ -407,7 +411,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return elif self._authentication_state != AuthState.Authenticated: self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", - "Not authenticated to print with this machine. Unable to start a new job.")) + "No access to print with this printer. Unable to start a new job.")) self._not_authenticated_message.show() Logger.log("d", "Attempting to perform an action without authentication. Auth state is %s", self._authentication_state) return @@ -424,14 +428,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "": Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1) self._error_message = Message( - i18n_catalog.i18nc("@info:status", "Unable to start a new print job; no PrinterCore loaded in slot {0}".format(index + 1))) + i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No PrinterCore loaded in slot {0}".format(index + 1))) self._error_message.show() return if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"] == "": Logger.log("e", "No material loaded in slot %s, unable to start print", index + 1) self._error_message = Message( i18n_catalog.i18nc("@info:status", - "Unable to start a new print job; no material loaded in slot {0}".format(index + 1))) + "Unable to start a new print job. No material loaded in slot {0}".format(index + 1))) self._error_message.show() return From b82889f84e6e772b2eaa5a75da9a51cdf8beb0dc Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 12 Sep 2016 15:59:45 +0200 Subject: [PATCH 215/297] Only show access request message when the user requested access --- NetworkPrinterOutputDevice.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 48a9554a50..674b35343b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -122,6 +122,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_timer.setInterval(1000) # TODO; Add preference for update interval self._authentication_timer.setSingleShot(False) self._authentication_timer.timeout.connect(self._onAuthenticationTimer) + self._authentication_request_active = False self._authentication_state = AuthState.NotAuthenticated self._authentication_id = None @@ -129,9 +130,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer requested. Please approve the request on the printer"), lifetime = 0, dismissable = False, progress = 0) self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "")) - self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry "), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the access request")) + self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry"), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the access request")) self._authentication_failed_message.actionTriggered.connect(self.messageActionTriggered) self._authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer accepted")) + self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", "No access to print with this printer. Unable to send print job.")) + self._not_authenticated_message.addAction("Request", i18n_catalog.i18nc("@action:button", "Request Access"), None, i18n_catalog.i18nc("@info:tooltip", "Send access request to the printer")) + self._not_authenticated_message.actionTriggered.connect(self.messageActionTriggered) self._camera_image = QImage() @@ -142,7 +146,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._response_timeout_time = 10 self._recreate_network_manager_time = 30 # If we have no connection, re-create network manager every 30 sec. self._recreate_network_manager_count = 1 - self._not_authenticated_message = None + self._send_gcode_start = time() # Time when the sending of the g-code started. self._last_command = "" @@ -204,13 +208,15 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setAcceptsCommands(False) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}. Please approve the access request on the printer.").format(self.name)) self._authentication_requested_message.show() + self._authentication_request_active = True self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: Logger.log("d", "Authentication state changed to authenticated") self.setAcceptsCommands(True) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}.").format(self.name)) self._authentication_requested_message.hide() - self._authentication_succeeded_message.show() + if self._authentication_request_active: + self._authentication_succeeded_message.show() # Stop waiting for a response self._authentication_timer.stop() @@ -223,11 +229,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setAcceptsCommands(False) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}. No access to control the printer.").format(self.name)) self._authentication_requested_message.hide() - if self._authentication_timer.remainingTime() > 0: - self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request was denied on the printer.")) - else: - self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request failed due to a timeout.")) - self._authentication_failed_message.show() + if self._authentication_request_active: + if self._authentication_timer.remainingTime() > 0: + self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request was denied on the printer.")) + else: + self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request failed due to a timeout.")) + + self._authentication_failed_message.show() + self._authentication_request_active = False # Stop waiting for a response self._authentication_timer.stop() @@ -237,6 +246,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def messageActionTriggered(self, message_id, action_id): self._authentication_failed_message.hide() + self._not_authenticated_message.hide() self._authentication_state = AuthState.NotAuthenticated self._authentication_counter = 0 self._authentication_requested_message.setProgress(0) @@ -410,8 +420,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._error_message.show() return elif self._authentication_state != AuthState.Authenticated: - self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", - "No access to print with this printer. Unable to start a new job.")) self._not_authenticated_message.show() Logger.log("d", "Attempting to perform an action without authentication. Auth state is %s", self._authentication_state) return From e94540384512b23f8183e2c483860d1160b046c4 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 12 Sep 2016 18:53:12 +0200 Subject: [PATCH 216/297] Make requesting authentication public Contributes to CURA-2277 --- NetworkPrinterOutputDevice.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 674b35343b..5d70005d5f 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -131,11 +131,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer requested. Please approve the request on the printer"), lifetime = 0, dismissable = False, progress = 0) self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", "")) self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry"), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the access request")) - self._authentication_failed_message.actionTriggered.connect(self.messageActionTriggered) + self._authentication_failed_message.actionTriggered.connect(self.requestAuthentication) self._authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer accepted")) self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", "No access to print with this printer. Unable to send print job.")) self._not_authenticated_message.addAction("Request", i18n_catalog.i18nc("@action:button", "Request Access"), None, i18n_catalog.i18nc("@info:tooltip", "Send access request to the printer")) - self._not_authenticated_message.actionTriggered.connect(self.messageActionTriggered) + self._not_authenticated_message.actionTriggered.connect(self.requestAuthentication) self._camera_image = QImage() @@ -244,7 +244,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_state = auth_state - def messageActionTriggered(self, message_id, action_id): + @pyqtSlot() + def requestAuthentication(self, message_id, action_id): self._authentication_failed_message.hide() self._not_authenticated_message.hide() self._authentication_state = AuthState.NotAuthenticated From c5d0942ee29cf2adfdb53b3a09b2c78ba870d788 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 12 Sep 2016 18:54:28 +0200 Subject: [PATCH 217/297] Add "Connect" and "Request Access" buttons to sidebar monitor Functionality is not 100% proven, but the required strings are in CURA-2277 --- DiscoverUM3Action.py | 29 +++++++++++++++-- UM3InfoComponents.qml | 76 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 UM3InfoComponents.qml diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 7115822582..747b568e25 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -1,8 +1,13 @@ from cura.MachineAction import MachineAction from UM.Application import Application +from UM.PluginRegistry import PluginRegistry +from UM.Logger import Logger -from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot +from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QUrl, QObject +from PyQt5.QtQml import QQmlComponent, QQmlContext + +import os.path from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -14,6 +19,12 @@ class DiscoverUM3Action(MachineAction): self._network_plugin = None + self._context = None + self._additional_component = None + self._additional_components_view = None + + Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) + printersChanged = pyqtSignal() @pyqtSlot() @@ -68,4 +79,18 @@ class DiscoverUM3Action(MachineAction): if "um_network_key" in meta_data: return global_container_stack.getMetaDataEntry("um_network_key") - return "" \ No newline at end of file + return "" + + def _createAdditionalComponentsView(self): + Logger.log("d", "Creating additional ui components for UM3.") + + path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("JediWifiPrintingPlugin"), "UM3InfoComponents.qml")) + self._additional_component = QQmlComponent(Application.getInstance()._engine, path) + + # We need access to engine (although technically we can't) + self._context = QQmlContext(Application.getInstance()._engine.rootContext()) + self._context.setContextProperty("manager", self) + self._additional_components_view = self._additional_component.create(self._context) + + Application.getInstance().addAdditionalComponent("monitorButtons", self._additional_components_view.findChild(QObject, "networkPrinterConnectButton")) + Application.getInstance().addAdditionalComponent("machinesDetailPane", self._additional_components_view.findChild(QObject, "networkPrinterConnectionInfo")) diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml new file mode 100644 index 0000000000..0106c8cffa --- /dev/null +++ b/UM3InfoComponents.qml @@ -0,0 +1,76 @@ +import UM 1.2 as UM +import Cura 1.0 as Cura + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +Item +{ + id: base + + property bool isUM3: Cura.MachineManager.activeDefinitionId == "ultimaker3" + property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 + property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands + + Row + { + objectName: "networkPrinterConnectButton" + visible: isUM3 + spacing: UM.Theme.getSize("default_marging").width + + Button + { + height: UM.Theme.getSize("save_button_save_to_button").height + tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer") + text: catalog.i18nc("@action:button", "Request Access") + style: UM.Theme.styles.sidebar_action_button + onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() + visible: base.printerConnected && !base.printerAcceptsCommands + } + + Button + { + height: UM.Theme.getSize("save_button_save_to_button").height + tooltip: catalog.i18nc("@info:tooltip", "Connect to a printer") + text: catalog.i18nc("@action:button", "Connect") + style: UM.Theme.styles.sidebar_action_button + onClicked: connectActionDialog.show() + visible: !base.printerConnected + } + } + + UM.Dialog + { + id: connectActionDialog + Loader + { + anchors.fill: parent + source: "DiscoverUM3Action.qml" + } + rightButtons: Button + { + text: catalog.i18nc("@action:button", "Close") + iconName: "dialog-close" + onClicked: connectActionDialog.reject() + } + } + + + Item + { + objectName: "networkPrinterConnectionInfo" + visible: isUM3 + Button + { + height: UM.Theme.getSize("save_button_save_to_button").height + tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer") + text: catalog.i18nc("@action:button", "Request Access") + onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() + visible: base.printerConnected && !base.printerAcceptsCommands + } + } + + UM.I18nCatalog{id: catalog; name:"cura"} +} \ No newline at end of file From 6435e8ce6f9e21440181344292f97b6faedcb5f2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 13 Sep 2016 09:49:17 +0200 Subject: [PATCH 218/297] Request is now only triggered on right actions CURA-2279 --- NetworkPrinterOutputDevice.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 5d70005d5f..239076383d 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -246,14 +246,15 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): @pyqtSlot() def requestAuthentication(self, message_id, action_id): - self._authentication_failed_message.hide() - self._not_authenticated_message.hide() - self._authentication_state = AuthState.NotAuthenticated - self._authentication_counter = 0 - self._authentication_requested_message.setProgress(0) - self._authentication_id = None - self._authentication_key = None - self._createNetworkManager() # Re-create network manager to force re-authentication. + if action_id == "Request" or action_id == "Retry": + self._authentication_failed_message.hide() + self._not_authenticated_message.hide() + self._authentication_state = AuthState.NotAuthenticated + self._authentication_counter = 0 + self._authentication_requested_message.setProgress(0) + self._authentication_id = None + self._authentication_key = None + self._createNetworkManager() # Re-create network manager to force re-authentication. ## Request data from the connected device. def _update(self): From 6069182175e8841ce08e0dde27052e41b72bd857 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 13 Sep 2016 09:50:04 +0200 Subject: [PATCH 219/297] Added UM3InfoComponents.qml to CMakeLists to the file also gets installed in the build --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 618e8ecbbe..c551e7f707 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ install(FILES DiscoverUM3Action.qml NetworkPrinterOutputDevice.py NetworkPrinterOutputDevicePlugin.py + UM3InfoComponents.qml LICENSE README.md DESTINATION lib/cura/plugins/JediWifiPrintingPlugin From 1ce6c3a4f1737d26f0f9fb269168cea6b8d0ea1c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 13 Sep 2016 10:53:41 +0200 Subject: [PATCH 220/297] Added defaults to requestAcces Cura-2277 --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 239076383d..921a704d46 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -245,7 +245,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_state = auth_state @pyqtSlot() - def requestAuthentication(self, message_id, action_id): + def requestAuthentication(self, message_id = None, action_id = "Retry"): if action_id == "Request" or action_id == "Retry": self._authentication_failed_message.hide() self._not_authenticated_message.hide() From a266a4026c4ee193564dd61aa0e79cadc07e2026 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 13 Sep 2016 11:02:45 +0200 Subject: [PATCH 221/297] Added explicit enabled property so connect button actually reacts to clicks CURA-2277 --- UM3InfoComponents.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index 0106c8cffa..4ce4cc0f92 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -37,6 +37,7 @@ Item text: catalog.i18nc("@action:button", "Connect") style: UM.Theme.styles.sidebar_action_button onClicked: connectActionDialog.show() + enabled: true visible: !base.printerConnected } } From 5c4f79eab3804cb1c0c1565e4a0d80db3aaa39e1 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 13 Sep 2016 11:03:51 +0200 Subject: [PATCH 222/297] Add stub button to sync configuration from printer to Cura inb4 string-freeze CURA-2276 --- UM3InfoComponents.qml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index 0106c8cffa..c51b33e688 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -64,12 +64,18 @@ Item visible: isUM3 Button { - height: UM.Theme.getSize("save_button_save_to_button").height tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer") text: catalog.i18nc("@action:button", "Request Access") onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() visible: base.printerConnected && !base.printerAcceptsCommands } + + Button + { + tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") + text: catalog.i18nc("@action:button", "Activate Configuration") + visible: false + } } UM.I18nCatalog{id: catalog; name:"cura"} From cb05bbadd0b037e23ee3d91a966eeb0d7d3f2ce7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 13 Sep 2016 11:05:09 +0200 Subject: [PATCH 223/297] Explicitly set it to bool to prevent warnings --- DiscoverUM3Action.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index a33ddd31fe..c98bb13334 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -165,7 +165,7 @@ Cura.MachineAction Column { width: parent.width * 0.5 - visible: base.selectedPrinter + visible: base.selectedPrinter ? true : false spacing: UM.Theme.getSize("default_margin").height Label { @@ -220,7 +220,7 @@ Cura.MachineAction Button { text: catalog.i18nc("@action:button", "Connect") - enabled: base.selectedPrinter + enabled: base.selectedPrinter ? true : false onClicked: connectToPrinter() } } From 1cf92d432d709fbf2880c61fabb8ffecbcb5031f Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 13 Sep 2016 11:46:55 +0200 Subject: [PATCH 224/297] Typo. CURA-2277 --- UM3InfoComponents.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index 0106c8cffa..d3f4818ccd 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -18,7 +18,7 @@ Item { objectName: "networkPrinterConnectButton" visible: isUM3 - spacing: UM.Theme.getSize("default_marging").width + spacing: UM.Theme.getSize("default_margin").width Button { From 400401929d7dd224ef58b6729046faf6c97d7afa Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 13 Sep 2016 14:36:36 +0200 Subject: [PATCH 225/297] User message now also states what the mismatch between cura and printer is CURA-2285 --- NetworkPrinterOutputDevice.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 921a704d46..612f2932c7 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -462,18 +462,25 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): extruder_manager = cura.Settings.ExtruderManager.getInstance() if print_information.materialLengths[index] != 0: variant = extruder_manager.getExtruderStack(index).findContainer({"type": "variant"}) + core_name = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] if variant: - if variant.getName() != self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]: - Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"], variant.getName()) - warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore selected for extruder {0}".format(index + 1))) + if variant.getName() != core_name: + Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, core_name, variant.getName()) + warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}".format(variant.getName(), core_name, index + 1))) material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) if material: - if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"]: + remote_material_guid = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"] + if material.getMetaDataEntry("GUID") != remote_material_guid: Logger.log("w", "Extruder %s has a different material (%s) as Cura (%s)", index + 1, - self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"], + remote_material_guid, material.getMetaDataEntry("GUID")) - warnings.append(i18n_catalog.i18nc("@label", "Different material selected for extruder {0}").format(index + 1)) + + remote_materials = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material", GUID = remote_material_guid, read_only = True) + remote_material_name = "Unknown" + if remote_materials: + remote_material_name = remote_materials[0].getName() + warnings.append(i18n_catalog.i18nc("@label", "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}").format(material.getName(), remote_material_name, index + 1)) if warnings: text = i18n_catalog.i18nc("@label", "Are you sure you wish to print with the selected configuration?") From a754952f5ef0f1c56121416c48d1247e8cf9a670 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 13 Sep 2016 17:43:57 +0200 Subject: [PATCH 226/297] Close connection now also ensures that cached machine & material ids are reset CURA-2354 --- NetworkPrinterOutputDevice.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 612f2932c7..291dedab1a 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -400,6 +400,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_failed_message.hide() self._authentication_succeeded_message.hide() + # Reset stored material & hotend data. + self._material_ids = [""] * self._num_extruders + self._hotend_ids = [""] * self._num_extruders + if self._error_message: self._error_message.hide() From cc38705a6846a72b6e79987fe78e9bca6b0f7686 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 14 Sep 2016 12:55:11 +0200 Subject: [PATCH 227/297] Use right extruder for material --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 291dedab1a..fb15428cf1 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -472,7 +472,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, core_name, variant.getName()) warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}".format(variant.getName(), core_name, index + 1))) - material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) + material = extruder_manager.getExtruderStack(index).findContainer({"type": "material"}) if material: remote_material_guid = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"] if material.getMetaDataEntry("GUID") != remote_material_guid: From 15d933ed73aba36e03ef120e9f53b456b7711a5a Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 14 Sep 2016 15:00:41 +0200 Subject: [PATCH 228/297] Show Extruders, PrintCores, Materials on Printer preference pane CURA-2276 --- UM3InfoComponents.qml | 63 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index 9997978c79..55ddf7b98e 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -59,23 +59,66 @@ Item } - Item + Column { objectName: "networkPrinterConnectionInfo" visible: isUM3 - Button + spacing: UM.Theme.getSize("default_margin").width + anchors.fill: parent + + Row { - tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer") - text: catalog.i18nc("@action:button", "Request Access") - onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() - visible: base.printerConnected && !base.printerAcceptsCommands + visible: base.printerConnected + spacing: UM.Theme.getSize("default_margin").width + + anchors.left: parent.left + anchors.right: parent.right + height: childrenRect.height + + Column + { + Repeater + { + model: Cura.ExtrudersModel { simpleNames: true } + Label { text: model.name } + } + } + Column + { + Repeater + { + id: nozzleColumn + model: Cura.MachineManager.printerOutputDevices[0].hotendIds + Label { text: nozzleColumn.model[index] } + } + } + Column + { + Repeater + { + id: materialColumn + model: Cura.MachineManager.printerOutputDevices[0].materialNames + Label { text: materialColumn.model[index] } + } + } } - Button + Row { - tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") - text: catalog.i18nc("@action:button", "Activate Configuration") - visible: false + Button + { + tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") + text: catalog.i18nc("@action:button", "Activate Configuration") + visible: base.printerConnected && false + } + + Button + { + tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer") + text: catalog.i18nc("@action:button", "Request Access") + onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() + visible: base.printerConnected && !base.printerAcceptsCommands + } } } From f11308e74e86835212258c9fcfd9829618e4263f Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 14 Sep 2016 16:21:00 +0200 Subject: [PATCH 229/297] Allow syncing materials & printcores from the Printers preference pane CURA-2276 --- DiscoverUM3Action.py | 10 ++++++++++ UM3InfoComponents.qml | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 747b568e25..845b7eb4a0 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -81,6 +81,16 @@ class DiscoverUM3Action(MachineAction): return "" + @pyqtSlot() + def loadConfigurationFromPrinter(self): + machine_manager = Application.getInstance().getMachineManager() + hotendIds = machine_manager.printerOutputDevices[0].hotendIds + for index in range(len(hotendIds)): + machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotendIds[index]) + materialIds = machine_manager.printerOutputDevices[0].materialIds + for index in range(len(materialIds)): + machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, materialIds[index]) + def _createAdditionalComponentsView(self): Logger.log("d", "Creating additional ui components for UM3.") diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index 55ddf7b98e..67e52aa3ab 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -109,7 +109,8 @@ Item { tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") text: catalog.i18nc("@action:button", "Activate Configuration") - visible: base.printerConnected && false + visible: base.printerConnected + onClicked: manager.loadConfigurationFromPrinter() } Button From 20f76b62fefb558615521a911d66535d3def0313 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 14 Sep 2016 16:40:20 +0200 Subject: [PATCH 230/297] Hide "Request Access" buttons when access request is active CURA-2277 & CURA-2276 --- NetworkPrinterOutputDevice.py | 10 +++++++++- UM3InfoComponents.qml | 32 +++++++++++++++----------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index fb15428cf1..2fef90ac86 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -242,7 +242,15 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_timer.stop() self._authentication_counter = 0 - self._authentication_state = auth_state + if auth_state != self._authentication_state: + self._authentication_state = auth_state + self.authenticationStateChanged.emit() + + authenticationStateChanged = pyqtSignal() + + @pyqtProperty(int, notify = authenticationStateChanged) + def authenticationState(self): + return self._authentication_state @pyqtSlot() def requestAuthentication(self, message_id = None, action_id = "Retry"): diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index 67e52aa3ab..686a13e2cb 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -13,6 +13,7 @@ Item property bool isUM3: Cura.MachineManager.activeDefinitionId == "ultimaker3" property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands + property bool authenticationRequested: printerConnected && Cura.MachineManager.printerOutputDevices[0].authenticationState == 2 // AuthState.AuthenticationRequested Row { @@ -27,7 +28,7 @@ Item text: catalog.i18nc("@action:button", "Request Access") style: UM.Theme.styles.sidebar_action_button onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() - visible: base.printerConnected && !base.printerAcceptsCommands + visible: !base.printerAcceptsCommands && !base.authenticationRequested } Button @@ -66,6 +67,14 @@ Item spacing: UM.Theme.getSize("default_margin").width anchors.fill: parent + Button + { + tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer") + text: catalog.i18nc("@action:button", "Request Access") + onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() + visible: !base.printerAcceptsCommands && !base.authenticationRequested + } + Row { visible: base.printerConnected @@ -103,23 +112,12 @@ Item } } - Row + Button { - Button - { - tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") - text: catalog.i18nc("@action:button", "Activate Configuration") - visible: base.printerConnected - onClicked: manager.loadConfigurationFromPrinter() - } - - Button - { - tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer") - text: catalog.i18nc("@action:button", "Request Access") - onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() - visible: base.printerConnected && !base.printerAcceptsCommands - } + tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") + text: catalog.i18nc("@action:button", "Activate Configuration") + visible: base.printerConnected + onClicked: manager.loadConfigurationFromPrinter() } } From 34aeb50b6a29e138a77df401d1cf48c23a3bd318 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Sep 2016 10:21:32 +0200 Subject: [PATCH 231/297] self._code_style --- DiscoverUM3Action.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 845b7eb4a0..8f4afb36ab 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -84,12 +84,12 @@ class DiscoverUM3Action(MachineAction): @pyqtSlot() def loadConfigurationFromPrinter(self): machine_manager = Application.getInstance().getMachineManager() - hotendIds = machine_manager.printerOutputDevices[0].hotendIds - for index in range(len(hotendIds)): - machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotendIds[index]) - materialIds = machine_manager.printerOutputDevices[0].materialIds - for index in range(len(materialIds)): - machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, materialIds[index]) + hotend_ids = machine_manager.printerOutputDevices[0].hotendIds + for index in range(len(hotend_ids)): + machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotend_ids[index]) + material_ids = machine_manager.printerOutputDevices[0].materialIds + for index in range(len(material_ids)): + machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, material_ids[index]) def _createAdditionalComponentsView(self): Logger.log("d", "Creating additional ui components for UM3.") From 96e36c0555e0869b5da76df3612bc98bd6ed3efd Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 15 Sep 2016 13:32:13 +0200 Subject: [PATCH 232/297] Refreshing zeroconf multiple times no longer causes cura crash CURA-2393 --- DiscoverUM3Action.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 8f4afb36ab..82d9240819 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -9,6 +9,8 @@ from PyQt5.QtQml import QQmlComponent, QQmlContext import os.path +import time + from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -25,6 +27,9 @@ class DiscoverUM3Action(MachineAction): Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) + self._min_time_between_restart_discovery = 2 + self._time_since_last_discovery = time.time() - self._min_time_between_restart_discovery + printersChanged = pyqtSignal() @pyqtSlot() @@ -37,10 +42,17 @@ class DiscoverUM3Action(MachineAction): @pyqtSlot() def restartDiscovery(self): - if not self._network_plugin: - self.startDiscovery() - else: - self._network_plugin.startDiscovery() + # Ensure that there is a bit of time between refresh attempts. + # This is a work around for an issue with Qt 5.5.1 up to Qt 5.7 which can segfault if we do this too often. + # It's most likely that the QML engine is still creating delegates, where the python side already deleted or + # garbage collected the data. + # Whatever the case, waiting a bit ensures that it doesn't crash. + if time.time() - self._time_since_last_discovery > self._min_time_between_restart_discovery: + if not self._network_plugin: + self.startDiscovery() + else: + self._network_plugin.startDiscovery() + self._time_since_last_discovery = time.time() def _onPrinterDiscoveryChanged(self, *args): self.printersChanged.emit() From 00c328bfe9f344aa1b6f5695d172c002734fbc59 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Sep 2016 13:44:12 +0200 Subject: [PATCH 233/297] Fix qml warnings when no printer is connected --- UM3InfoComponents.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index 686a13e2cb..f0da9e627c 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -97,7 +97,7 @@ Item Repeater { id: nozzleColumn - model: Cura.MachineManager.printerOutputDevices[0].hotendIds + model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].hotendIds : null Label { text: nozzleColumn.model[index] } } } @@ -106,7 +106,7 @@ Item Repeater { id: materialColumn - model: Cura.MachineManager.printerOutputDevices[0].materialNames + model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].materialNames : null Label { text: materialColumn.model[index] } } } From cf2c3e79cfb3c8a06d4a2c3081a2bf00c63ca98b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Sep 2016 14:17:04 +0200 Subject: [PATCH 234/297] Do not show the "Request Access" button when no printer is connected CURA-2277 --- UM3InfoComponents.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index f0da9e627c..598e26a7fd 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -28,7 +28,7 @@ Item text: catalog.i18nc("@action:button", "Request Access") style: UM.Theme.styles.sidebar_action_button onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() - visible: !base.printerAcceptsCommands && !base.authenticationRequested + visible: printerConnected && !base.printerAcceptsCommands && !base.authenticationRequested } Button From 2725abd4f43a44492969e1476ef98fd2448e8138 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Sep 2016 14:49:15 +0200 Subject: [PATCH 235/297] Don't show Request Access button when no printer is connected CURA-2276 --- UM3InfoComponents.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index 598e26a7fd..b5a707b280 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -38,7 +38,6 @@ Item text: catalog.i18nc("@action:button", "Connect") style: UM.Theme.styles.sidebar_action_button onClicked: connectActionDialog.show() - enabled: true visible: !base.printerConnected } } @@ -72,7 +71,7 @@ Item tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer") text: catalog.i18nc("@action:button", "Request Access") onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() - visible: !base.printerAcceptsCommands && !base.authenticationRequested + visible: base.printerConnected && !base.printerAcceptsCommands && !base.authenticationRequested } Row From 5024581e2e79a5c3e590363ce6c689ffe5250479 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Sep 2016 17:42:36 +0200 Subject: [PATCH 236/297] Fix events for added components Not sharing the _context variable with the machine action fixes signals and events being sent to the additional components. CURA-2277 and CURA-2276 --- DiscoverUM3Action.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 82d9240819..0b0e61d585 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -21,7 +21,7 @@ class DiscoverUM3Action(MachineAction): self._network_plugin = None - self._context = None + self._additional_components_context = None self._additional_component = None self._additional_components_view = None @@ -110,9 +110,9 @@ class DiscoverUM3Action(MachineAction): self._additional_component = QQmlComponent(Application.getInstance()._engine, path) # We need access to engine (although technically we can't) - self._context = QQmlContext(Application.getInstance()._engine.rootContext()) - self._context.setContextProperty("manager", self) - self._additional_components_view = self._additional_component.create(self._context) + self._additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext()) + self._additional_components_context.setContextProperty("manager", self) + self._additional_components_view = self._additional_component.create(self._additional_components_context) Application.getInstance().addAdditionalComponent("monitorButtons", self._additional_components_view.findChild(QObject, "networkPrinterConnectButton")) Application.getInstance().addAdditionalComponent("machinesDetailPane", self._additional_components_view.findChild(QObject, "networkPrinterConnectionInfo")) From c50cae6d28268fab23fe1e615165f449c1dba1aa Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Sep 2016 17:43:25 +0200 Subject: [PATCH 237/297] Fix showing additional components for UM3Ex CURA-2276 and CURA-2277 --- UM3InfoComponents.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/UM3InfoComponents.qml b/UM3InfoComponents.qml index b5a707b280..a5ed944773 100644 --- a/UM3InfoComponents.qml +++ b/UM3InfoComponents.qml @@ -10,7 +10,7 @@ Item { id: base - property bool isUM3: Cura.MachineManager.activeDefinitionId == "ultimaker3" + property bool isUM3: Cura.MachineManager.activeQualityDefinitionId == "ultimaker3" property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands property bool authenticationRequested: printerConnected && Cura.MachineManager.printerOutputDevices[0].authenticationState == 2 // AuthState.AuthenticationRequested @@ -28,7 +28,7 @@ Item text: catalog.i18nc("@action:button", "Request Access") style: UM.Theme.styles.sidebar_action_button onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() - visible: printerConnected && !base.printerAcceptsCommands && !base.authenticationRequested + visible: printerConnected && !printerAcceptsCommands && !authenticationRequested } Button @@ -38,7 +38,7 @@ Item text: catalog.i18nc("@action:button", "Connect") style: UM.Theme.styles.sidebar_action_button onClicked: connectActionDialog.show() - visible: !base.printerConnected + visible: !printerConnected } } @@ -71,12 +71,12 @@ Item tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer") text: catalog.i18nc("@action:button", "Request Access") onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication() - visible: base.printerConnected && !base.printerAcceptsCommands && !base.authenticationRequested + visible: printerConnected && !printerAcceptsCommands && !authenticationRequested } Row { - visible: base.printerConnected + visible: printerConnected spacing: UM.Theme.getSize("default_margin").width anchors.left: parent.left @@ -115,7 +115,7 @@ Item { tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") text: catalog.i18nc("@action:button", "Activate Configuration") - visible: base.printerConnected + visible: printerConnected onClicked: manager.loadConfigurationFromPrinter() } } From 7840f6a82e0ed488ac7c376a77475e4ed6aaff91 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 16 Sep 2016 16:19:20 +0200 Subject: [PATCH 238/297] Increased time to 5 sec CURA-2393 --- DiscoverUM3Action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 82d9240819..641c4de9b5 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -27,7 +27,7 @@ class DiscoverUM3Action(MachineAction): Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) - self._min_time_between_restart_discovery = 2 + self._min_time_between_restart_discovery = 5 self._time_since_last_discovery = time.time() - self._min_time_between_restart_discovery printersChanged = pyqtSignal() From 24cd26b37ec9822a6fad3083ea1864be6ad43479 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 19 Sep 2016 08:40:29 +0200 Subject: [PATCH 239/297] Show mjpg stream instead of slideshow CURA-2411 --- NetworkPrinterOutputDevice.py | 47 +++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2fef90ac86..ecb493d85d 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -113,6 +113,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._camera_timer.setSingleShot(False) self._camera_timer.timeout.connect(self._update_camera) + self._image_request = None + self._image_reply = None + + self._use_stream = True + self._stream_buffer = b"" + self._stream_buffer_start_index = -1 + self._camera_image_id = 0 self._authentication_counter = 0 @@ -192,6 +199,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def ipAddress(self): return self._address + def _start_camera_stream(self): + ## Request new image + url = QUrl("http://" + self._address + ":8080/?action=stream") + self._image_request = QNetworkRequest(url) + self._image_reply = self._manager.get(self._image_request) + self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress) + def _update_camera(self): if not self._manager.networkAccessible(): return @@ -423,6 +437,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.stop() self._camera_timer.stop() + if self._image_reply: + self._image_reply.abort() + self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress) + self._image_reply = None + self._image_request = None + def requestWrite(self, node, file_name = None, filter_by_machine = False): if self._progress != 0: self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to start a new print job because the printer is busy. Please check the printer.")) @@ -531,7 +551,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setConnectionState(ConnectionState.connecting) self._update() # Manually trigger the first update, as we don't want to wait a few secs before it starts. - self._update_camera() + if not self._use_stream: + self._update_camera() Logger.log("d", "Connection with printer %s with ip %s started", self._key, self._address) ## Check if this machine was authenticated before. @@ -539,7 +560,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_key", None) self._update_timer.start() - self._camera_timer.start() + if self._use_stream: + self._start_camera_stream() + else: + self._camera_timer.start() ## Stop requesting data from printer def disconnect(self): @@ -841,6 +865,25 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): else: Logger.log("d", "NetworkPrinterOutputDevice got an unhandled operation %s", reply.operation()) + def _onStreamDownloadProgress(self, bytes_received, bytes_total): + # An MJPG stream is (for our purpose) a stream of concatenated JPG images. + # JPG images start with the marker 0xFFD8, and end with 0xFFD9 + self._stream_buffer += self._image_reply.readAll() + + if self._stream_buffer_start_index == -1: + self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8') + stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9') + # If this happens to be more than a single frame, then so be it; the JPG decoder will + # ignore the extra data. We do it like this in order not to get a buildup of frames + + if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1: + jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2] + self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:] + self._stream_buffer_start_index = -1 + + self._camera_image.loadFromData(jpg_data) + self.newImage.emit() + def _onUploadProgress(self, bytes_sent, bytes_total): if bytes_total > 0: new_progress = bytes_sent / bytes_total * 100 From d980f6b792941f08cd1c7db0d611806b2eacfead Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 19 Sep 2016 10:26:25 +0200 Subject: [PATCH 240/297] Code style CURA-2277/CURA-2276 --- DiscoverUM3Action.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index b940765de0..917824eccf 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -21,9 +21,9 @@ class DiscoverUM3Action(MachineAction): self._network_plugin = None - self._additional_components_context = None - self._additional_component = None - self._additional_components_view = None + self.__additional_components_context = None + self.__additional_component = None + self.__additional_components_view = None Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) @@ -107,12 +107,12 @@ class DiscoverUM3Action(MachineAction): Logger.log("d", "Creating additional ui components for UM3.") path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("JediWifiPrintingPlugin"), "UM3InfoComponents.qml")) - self._additional_component = QQmlComponent(Application.getInstance()._engine, path) + self.__additional_component = QQmlComponent(Application.getInstance()._engine, path) # We need access to engine (although technically we can't) - self._additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext()) - self._additional_components_context.setContextProperty("manager", self) - self._additional_components_view = self._additional_component.create(self._additional_components_context) + self.__additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext()) + self.__additional_components_context.setContextProperty("manager", self) + self.__additional_components_view = self.__additional_component.create(self.__additional_components_context) - Application.getInstance().addAdditionalComponent("monitorButtons", self._additional_components_view.findChild(QObject, "networkPrinterConnectButton")) - Application.getInstance().addAdditionalComponent("machinesDetailPane", self._additional_components_view.findChild(QObject, "networkPrinterConnectionInfo")) + Application.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")) + Application.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo")) From e388b8126b02d28f5abadf0b2a51c0d6a3983a8b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 19 Sep 2016 11:29:57 +0200 Subject: [PATCH 241/297] No longer re-create zeroConf object, as this crashes CURA-2393 --- DiscoverUM3Action.py | 2 +- NetworkPrinterOutputDevicePlugin.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index b940765de0..2995c99f5d 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -27,7 +27,7 @@ class DiscoverUM3Action(MachineAction): Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) - self._min_time_between_restart_discovery = 5 + self._min_time_between_restart_discovery = 1 self._time_since_last_discovery = time.time() - self._min_time_between_restart_discovery printersChanged = pyqtSignal() diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index bd930493be..edcd406fab 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -41,7 +41,6 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._browser = None self._old_printers = [printer_name for printer_name in self._printers] self._printers = {} - self._zero_conf.__init__() self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged]) From 66b22134ba06a1c9fabb81f201a9eb0c456d1e6e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 19 Sep 2016 11:31:20 +0200 Subject: [PATCH 242/297] Always hide progress message if connection is lost --- NetworkPrinterOutputDevice.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2fef90ac86..ddee244355 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -294,6 +294,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the network was lost.")) self._connection_message.show() + + if self._progress_message: + self._progress_message.hide() + # Check if we were uploading something. Abort if this is the case. # Some operating systems handle this themselves, others give weird issues. try: @@ -305,7 +309,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass # The disconnection can fail on mac in some cases. Ignore that. self._post_reply.abort() - self._progress_message.hide() except RuntimeError: self._post_reply = None # It can happen that the wrapped c++ object is already deleted. return @@ -321,6 +324,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_state_before_timeout = self._connection_state self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your printer to see if it is connected.")) self._connection_message.show() + + if self._progress_message: + self._progress_message.hide() + # Check if we were uploading something. Abort if this is the case. # Some operating systems handle this themselves, others give weird issues. try: @@ -332,7 +339,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass # The disconnection can fail on mac in some cases. Ignore that. self._post_reply.abort() - self._progress_message.hide() except RuntimeError: self._post_reply = None # It can happen that the wrapped c++ object is already deleted. self.setConnectionState(ConnectionState.error) From 7da05e0f36137a88bf582f341508bbe5c45dd652 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 19 Sep 2016 13:38:52 +0200 Subject: [PATCH 243/297] Post reply is now set to None after abort CURA-2295 --- NetworkPrinterOutputDevice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index ddee244355..3d9a96d8ad 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -297,7 +297,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._progress_message: self._progress_message.hide() - + # Check if we were uploading something. Abort if this is the case. # Some operating systems handle this themselves, others give weird issues. try: @@ -309,6 +309,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass # The disconnection can fail on mac in some cases. Ignore that. self._post_reply.abort() + self._post_reply = None except RuntimeError: self._post_reply = None # It can happen that the wrapped c++ object is already deleted. return @@ -339,6 +340,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): pass # The disconnection can fail on mac in some cases. Ignore that. self._post_reply.abort() + self._post_reply = None except RuntimeError: self._post_reply = None # It can happen that the wrapped c++ object is already deleted. self.setConnectionState(ConnectionState.error) From 400360cb34e5efc699d14823fc70090828d90939 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 19 Sep 2016 15:05:33 +0200 Subject: [PATCH 244/297] Add check if additional components were successfully created --- DiscoverUM3Action.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 854e40e387..0d8c0ad060 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -112,7 +112,11 @@ class DiscoverUM3Action(MachineAction): # We need access to engine (although technically we can't) self.__additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext()) self.__additional_components_context.setContextProperty("manager", self) + self.__additional_components_view = self.__additional_component.create(self.__additional_components_context) + if not self.__additional_components_view: + Logger.log("w", "Could not create ui components for UM3.") + return Application.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")) Application.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo")) From f75e8c9537abc6ddf9d2526472453bb8e4205cc8 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 20 Sep 2016 11:53:14 +0200 Subject: [PATCH 245/297] Add version number --- __init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/__init__.py b/__init__.py index 6b7457ad36..be9f1195ec 100644 --- a/__init__.py +++ b/__init__.py @@ -11,6 +11,7 @@ def getMetaData(): "name": "UM3 Network Connection", "author": "Ultimaker", "description": catalog.i18nc("@info:whatsthis", "Manages network connections to Ultimaker 3 printers"), + "version": "1.0", "api": 3 } } From df9f940ff44dd13bc752a347a8123fe7756a64fc Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 20 Sep 2016 17:45:55 +0200 Subject: [PATCH 246/297] Added cancel button to upload print CURA-2384 --- NetworkPrinterOutputDevice.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 3d9a96d8ad..cb8ddde82e 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -151,6 +151,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._last_command = "" + self._compressing_print = False + def _onNetworkAccesibleChanged(self, accessible): Logger.log("d", "Network accessible state changed to: %s", accessible) @@ -585,6 +587,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return user return "Unknown User" # Couldn't find out username. + def _progressMessageActionTrigger(self, message_id = None, action_id="abort"): + if action_id == "abort": + Logger.log("d", "User aborted sending print to remote.") + self._progress_message.hide() + self._compressing_print = False + if self._post_reply: + self._post_reply.abort() + ## Attempt to start a new print. # This function can fail to actually start a print due to not being authenticated or another print already # being in progress. @@ -592,12 +602,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): try: self._send_gcode_start = time() self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) + self._progress_message.addAction("abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "") + self._progress_message.actionTriggered.connect(self._progressMessageActionTrigger) self._progress_message.show() Logger.log("d", "Started sending g-code to remote printer.") - + self._compressing_print = True ## Mash the data into single string byte_array_file_data = b"" for line in self._gcode: + if not self._compressing_print: + self._progress_message.hide() + return # Stop trying to zip, abort was called. if self._use_gzip: byte_array_file_data += gzip.compress(line.encode("utf-8")) QCoreApplication.processEvents() # Ensure that the GUI does not freeze. @@ -611,6 +626,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): else: file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName + self._compressing_print = False ## Create multi_part request self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType) From ba53b0109bcbdb1eba7f7857a8f4af7ceea96f04 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 21 Sep 2016 14:00:11 +0200 Subject: [PATCH 247/297] Fix connecting to the selected printer when pressing "Finish" in add machine wizard --- DiscoverUM3Action.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index c98bb13334..8b20066ab2 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -20,7 +20,7 @@ Cura.MachineAction onNextClicked: { // Connect to the printer if the MachineAction is currently shown - if(base.parent == dialog) + if(base.parent.wizard == dialog) { connectToPrinter(); } From d8f0b634aff9a600b7678219b6eb3b91c709e0bf Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 21 Sep 2016 14:01:05 +0200 Subject: [PATCH 248/297] Switch back to Print Setup when canceling sending a print to the printer CURA-2384 --- NetworkPrinterOutputDevice.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index cb8ddde82e..e213684d56 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -587,13 +587,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): return user return "Unknown User" # Couldn't find out username. - def _progressMessageActionTrigger(self, message_id = None, action_id="abort"): - if action_id == "abort": + def _progressMessageActionTrigger(self, message_id = None, action_id = None): + if action_id == "Abort": Logger.log("d", "User aborted sending print to remote.") self._progress_message.hide() self._compressing_print = False if self._post_reply: self._post_reply.abort() + Application.getInstance().showPrintMonitor.emit(False) ## Attempt to start a new print. # This function can fail to actually start a print due to not being authenticated or another print already @@ -602,7 +603,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): try: self._send_gcode_start = time() self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) - self._progress_message.addAction("abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "") + self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "") self._progress_message.actionTriggered.connect(self._progressMessageActionTrigger) self._progress_message.show() Logger.log("d", "Started sending g-code to remote printer.") From 24827ba5a1e47c7a28b85c5f5b8d005dab2bbf34 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 21 Sep 2016 15:59:26 +0200 Subject: [PATCH 249/297] Added more logging when authentication was denied CURA-2270 --- NetworkPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index cb8ddde82e..d6b7ff22e4 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -801,6 +801,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif status_code == 403: # If we already had an auth (eg; didn't request one), we only need a single 403 to see it as denied. if self._authentication_state != AuthState.AuthenticationRequested: + Logger.log("d", "While trying to verify the authentication state, we got a forbidden response. Our own auth state was %s", self._authentication_state) self.setAuthenticationState(AuthState.AuthenticationDenied) elif status_code == 200: self.setAuthenticationState(AuthState.Authenticated) From bc76744ddb5f926950272d2368981eea0d1d8634 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 21 Sep 2016 16:40:42 +0200 Subject: [PATCH 250/297] Add (more) logging to authentication denied state CURA-2270 --- NetworkPrinterOutputDevice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 7c0e78d0bb..cc46c2a3a5 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -227,14 +227,15 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Once we are authenticated we need to send all material profiles. self.sendMaterialProfiles() elif auth_state == AuthState.AuthenticationDenied: - Logger.log("d", "Authentication state changed to authentication denied") self.setAcceptsCommands(False) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}. No access to control the printer.").format(self.name)) self._authentication_requested_message.hide() if self._authentication_request_active: if self._authentication_timer.remainingTime() > 0: + Logger.logException("d", "Authentication state changed to authentication denied before the request timeout.") self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request was denied on the printer.")) else: + Logger.logException("d", "Authentication state changed to authentication denied due to a timeout") self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request failed due to a timeout.")) self._authentication_failed_message.show() From 64571bff0b8fb5daba0ea35c18af87d73c732f85 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 21 Sep 2016 16:42:02 +0200 Subject: [PATCH 251/297] Revert exception logging --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index cc46c2a3a5..2ec9e1bd92 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -232,10 +232,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_requested_message.hide() if self._authentication_request_active: if self._authentication_timer.remainingTime() > 0: - Logger.logException("d", "Authentication state changed to authentication denied before the request timeout.") + Logger.log("d", "Authentication state changed to authentication denied before the request timeout.") self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request was denied on the printer.")) else: - Logger.logException("d", "Authentication state changed to authentication denied due to a timeout") + Logger.log("d", "Authentication state changed to authentication denied due to a timeout") self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request failed due to a timeout.")) self._authentication_failed_message.show() From c0839bcfbbe12c77b33f1d9a374651117042af6a Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 23 Sep 2016 16:50:57 +0200 Subject: [PATCH 252/297] Correct and format the documentation example json --- NetworkPrinterOutputDevice.py | 51 ++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2ec9e1bd92..1a3e0db480 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -49,25 +49,38 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # This holds the full JSON file that was received from the last request. # The JSON looks like: - # {'led': {'saturation': 0.0, 'brightness': 100.0, 'hue': 0.0}, - # 'beep': {}, 'network': {'wifi_networks': [], - # 'ethernet': {'connected': True, 'enabled': True}, - # 'wifi': {'ssid': 'xxxx', 'connected': False, 'enabled': False}}, - # 'diagnostics': {}, - # 'bed': {'temperature': {'target': 60.0, 'current': 44.4}}, - # 'heads': [{'max_speed': {'z': 40.0, 'y': 300.0, 'x': 300.0}, - # 'position': {'z': 20.0, 'y': 6.0, 'x': 180.0}, - # 'fan': 0.0, - # 'jerk': {'z': 0.4, 'y': 20.0, 'x': 20.0}, - # 'extruders': [ - # {'feeder': {'max_speed': 45.0, 'jerk': 5.0, 'acceleration': 3000.0}, - # 'active_material': {'GUID': 'xxxxxxx', 'length_remaining': -1.0}, - # 'hotend': {'temperature': {'target': 0.0, 'current': 22.8}, 'id': 'AA 0.4'}}, - # {'feeder': {'max_speed': 45.0, 'jerk': 5.0, 'acceleration': 3000.0}, - # 'active_material': {'GUID': 'xxxx', 'length_remaining': -1.0}, - # 'hotend': {'temperature': {'target': 0.0, 'current': 22.8}, 'id': 'BB 0.4'}}], - # 'acceleration': 3000.0}], - # 'status': 'printing'} + #{ + # "led": {"saturation": 0.0, "brightness": 100.0, "hue": 0.0}, + # "beep": {}, + # "network": { + # "wifi_networks": [], + # "ethernet": {"connected": true, "enabled": true}, + # "wifi": {"ssid": "xxxx", "connected": False, "enabled": False} + # }, + # "diagnostics": {}, + # "bed": {"temperature": {"target": 60.0, "current": 44.4}}, + # "heads": [{ + # "max_speed": {"z": 40.0, "y": 300.0, "x": 300.0}, + # "position": {"z": 20.0, "y": 6.0, "x": 180.0}, + # "fan": 0.0, + # "jerk": {"z": 0.4, "y": 20.0, "x": 20.0}, + # "extruders": [ + # { + # "feeder": {"max_speed": 45.0, "jerk": 5.0, "acceleration": 3000.0}, + # "active_material": {"GUID": "xxxxxxx", "length_remaining": -1.0}, + # "hotend": {"temperature": {"target": 0.0, "current": 22.8}, "id": "AA 0.4"} + # }, + # { + # "feeder": {"max_speed": 45.0, "jerk": 5.0, "acceleration": 3000.0}, + # "active_material": {"GUID": "xxxx", "length_remaining": -1.0}, + # "hotend": {"temperature": {"target": 0.0, "current": 22.8}, "id": "BB 0.4"} + # } + # ], + # "acceleration": 3000.0 + # }], + # "status": "printing" + #} + self._json_printer_state = {} ## Todo: Hardcoded value now; we should probably read this from the machine file. From b4ba7a64a98bb5a6514433f4cf6b9b49d9872c4f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 26 Sep 2016 12:01:54 +0200 Subject: [PATCH 253/297] Only update printer list after printer is added/removed This makes it emit a signal only after addPrinter and removePrinter has completed executing, so we know that updating the list is done by the time it refreshes the list view in QML. Contributes to issue CURA-2393. --- DiscoverUM3Action.py | 3 +-- NetworkPrinterOutputDevicePlugin.py | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 0d8c0ad060..95105425c1 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -36,8 +36,7 @@ class DiscoverUM3Action(MachineAction): def startDiscovery(self): if not self._network_plugin: self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("JediWifiPrintingPlugin") - self._network_plugin.addPrinterSignal.connect(self._onPrinterDiscoveryChanged) - self._network_plugin.removePrinterSignal.connect(self._onPrinterDiscoveryChanged) + self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged) self.printersChanged.emit() @pyqtSlot() diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index edcd406fab..a168f2ae43 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -30,6 +30,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): addPrinterSignal = Signal() removePrinterSignal = Signal() + printerListChanged = Signal() ## Start looking for devices on network. def start(self): @@ -73,6 +74,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if printer.getKey() not in self._old_printers: # Was the printer already connected, but a re-scan forced? self._printers[printer.getKey()].connect() printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) + self.printerListChanged.emit() def removePrinter(self, name): printer = self._printers.pop(name, None) @@ -80,6 +82,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if printer.isConnected(): printer.connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged) printer.disconnect() + self.printerListChanged.emit() ## Handler for when the connection state of one of the detected printers changes def _onPrinterConnectionStateChanged(self, key): From 368851b9101b1b81f46d3b884ffb8f85c548afa8 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 26 Sep 2016 14:07:16 +0200 Subject: [PATCH 254/297] Check if a request was made recently before claiming a timeout occurred If the main thread locks up for longer than the network timeout period, the connection may incorrectly seem to have reached a timeout. CURA-2440 --- NetworkPrinterOutputDevice.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 1a3e0db480..acfc566b02 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -156,6 +156,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_state_before_timeout = None self._last_response_time = time() + self._last_request_time = None self._response_timeout_time = 10 self._recreate_network_manager_time = 30 # If we have no connection, re-create network manager every 30 sec. self._recreate_network_manager_count = 1 @@ -286,6 +287,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): time_since_last_response = time() - self._last_response_time else: time_since_last_response = 0 + if self._last_request_time: + time_since_last_request = time() - self._last_request_time + else: + time_since_last_request = 1000000 # An irrelevantly large number of seconds # Connection is in timeout, check if we need to re-start the connection. # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows. @@ -334,8 +339,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._recreate_network_manager_count = 1 # Check that we aren't in a timeout state - if self._last_response_time and not self._connection_state_before_timeout: - if time_since_last_response > self._response_timeout_time: + if self._last_response_time and self._last_request_time and not self._connection_state_before_timeout: + if time_since_last_response > self._response_timeout_time and time_since_last_request <= self._response_timeout_time: # Go into timeout state. Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accessible.", time_since_last_response) self._connection_state_before_timeout = self._connection_state @@ -377,6 +382,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): print_job_request = QNetworkRequest(url) self._manager.get(print_job_request) + self._last_request_time = time() + def _createNetworkManager(self): if self._manager: self._manager.finished.disconnect(self._onFinished) From 720dd9c50cf85d511e05366f12636ccc85240558 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 26 Sep 2016 16:28:06 +0200 Subject: [PATCH 255/297] Use the semantically more correct infinity --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index acfc566b02..e2fe36f118 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -290,7 +290,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._last_request_time: time_since_last_request = time() - self._last_request_time else: - time_since_last_request = 1000000 # An irrelevantly large number of seconds + time_since_last_request = float("inf") # An irrelevantly large number of seconds # Connection is in timeout, check if we need to re-start the connection. # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows. From fefa111b5aabb5f9951776d12b167996e6781fdf Mon Sep 17 00:00:00 2001 From: awhiemstra Date: Tue, 27 Sep 2016 15:52:31 +0200 Subject: [PATCH 256/297] Update references to JediWifiPrinting to UM3NetworkPrinting Contributes to CURA-2342 --- DiscoverUM3Action.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 95105425c1..c33e6aed02 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -35,7 +35,7 @@ class DiscoverUM3Action(MachineAction): @pyqtSlot() def startDiscovery(self): if not self._network_plugin: - self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("JediWifiPrintingPlugin") + self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting") self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged) self.printersChanged.emit() @@ -105,7 +105,7 @@ class DiscoverUM3Action(MachineAction): def _createAdditionalComponentsView(self): Logger.log("d", "Creating additional ui components for UM3.") - path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("JediWifiPrintingPlugin"), "UM3InfoComponents.qml")) + path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml")) self.__additional_component = QQmlComponent(Application.getInstance()._engine, path) # We need access to engine (although technically we can't) From 7408f31738434217455db842070a9d607b464cb2 Mon Sep 17 00:00:00 2001 From: awhiemstra Date: Tue, 27 Sep 2016 15:53:33 +0200 Subject: [PATCH 257/297] Change install location to UM3NetworkPrinting Contributes to CURA-2342 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c551e7f707..8e9db68ddf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -project(JediWifiPrintingPlugin NONE) +project(UM3NetworkPrinting NONE) cmake_minimum_required(VERSION 2.8.12) install(FILES @@ -10,5 +10,5 @@ install(FILES UM3InfoComponents.qml LICENSE README.md - DESTINATION lib/cura/plugins/JediWifiPrintingPlugin + DESTINATION lib/cura/plugins/UM3NetworkPrinting ) From fb371f55561f776683d357562d81e892d7866532 Mon Sep 17 00:00:00 2001 From: awhiemstra Date: Tue, 27 Sep 2016 15:57:01 +0200 Subject: [PATCH 258/297] Remove some more references to Jedi Contributes to CURA-2342 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 277111412c..0e88ce593b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# JediWifiPrintingPlugin -Secret plugin to enable wifi printing from Cura to JediPrinter +# UM3NetworkPrintingPlugin +Secret plugin to enable wifi printing from Cura to UM3 Intructions ---- -- Clone repo into [Cura installation folder]/plugins/JediWifiPrintingPlugin (Or somewhere else and add a link..) +- Clone repo into [Cura installation folder]/plugins/UM3NetworkPrinting (Or somewhere else and add a link..) - pip3 install python3-zeroconf From 1ed3fc7c3716a65f46ea063a22f314ccb38a82f6 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 28 Sep 2016 11:31:52 +0200 Subject: [PATCH 259/297] Don't reset zeroconf until after (most) printers have been found CURA-2060 --- DiscoverUM3Action.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index c33e6aed02..cda6ea386a 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -27,8 +27,8 @@ class DiscoverUM3Action(MachineAction): Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) - self._min_time_between_restart_discovery = 1 - self._time_since_last_discovery = time.time() - self._min_time_between_restart_discovery + self._last_zeroconf_event_time = time.time() + self._zeroconf_change_grace_period = 0.25 # Time to wait after a zeroconf service change before allowing a zeroconf reset printersChanged = pyqtSignal() @@ -41,19 +41,19 @@ class DiscoverUM3Action(MachineAction): @pyqtSlot() def restartDiscovery(self): - # Ensure that there is a bit of time between refresh attempts. + # Ensure that there is a bit of time after a printer has been discovered. # This is a work around for an issue with Qt 5.5.1 up to Qt 5.7 which can segfault if we do this too often. # It's most likely that the QML engine is still creating delegates, where the python side already deleted or # garbage collected the data. # Whatever the case, waiting a bit ensures that it doesn't crash. - if time.time() - self._time_since_last_discovery > self._min_time_between_restart_discovery: + if time.time() - self._last_zeroconf_event_time > self._zeroconf_change_grace_period: if not self._network_plugin: self.startDiscovery() else: self._network_plugin.startDiscovery() - self._time_since_last_discovery = time.time() def _onPrinterDiscoveryChanged(self, *args): + self._last_zeroconf_event_time = time.time() self.printersChanged.emit() @pyqtProperty("QVariantList", notify = printersChanged) From da4ea2e45024bc811d079aec889ecd4d7dcd96a3 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 28 Sep 2016 16:36:09 +0200 Subject: [PATCH 260/297] Better refresh button: now emits signal and re-instantiates zeroconf. Signal emits let the UI display an empty list. Re-instantiation copes with network changes. Contributes to CURA-2372 --- DiscoverUM3Action.py | 1 - NetworkPrinterOutputDevicePlugin.py | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index cda6ea386a..6c6757d28b 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -104,7 +104,6 @@ class DiscoverUM3Action(MachineAction): def _createAdditionalComponentsView(self): Logger.log("d", "Creating additional ui components for UM3.") - path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml")) self.__additional_component = QQmlComponent(Application.getInstance()._engine, path) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index a168f2ae43..f0966c9fe3 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -15,7 +15,7 @@ import time class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): def __init__(self): super().__init__() - self._zero_conf = Zeroconf() + self._zero_conf = None self._browser = None self._printers = {} @@ -37,17 +37,22 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self.startDiscovery() def startDiscovery(self): + self.stop() if self._browser: self._browser.cancel() self._browser = None self._old_printers = [printer_name for printer_name in self._printers] self._printers = {} - + self.printerListChanged.emit() + # After network switching, one must make a new instance of Zeroconf + # On windows, the instance creation is very fast (unnoticable). Other platforms? + self._zero_conf = Zeroconf() self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged]) ## Stop looking for devices on network. def stop(self): - self._zero_conf.close() + if self._zero_conf is not None: + self._zero_conf.close() def getPrinters(self): return self._printers From 8e26d63390d9d537908475dc0c9ae53d96a1be43 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 28 Sep 2016 17:45:56 +0200 Subject: [PATCH 261/297] Get a list of ipadresses/hosts to check for UM3 printers... ...in case discovery does not work. This uses the API to look up info on the printer instead of relying on zeroconf. CURA-2483 --- NetworkPrinterOutputDevicePlugin.py | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index a168f2ae43..caf20c5629 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -5,6 +5,10 @@ from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo from UM.Logger import Logger from UM.Signal import Signal, signalemitter from UM.Application import Application +from UM.Preferences import Preferences + +from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, QNetworkReply +from PyQt5.QtCore import QUrl import time @@ -19,6 +23,12 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._browser = None self._printers = {} + self._api_version = "1" + self._api_prefix = "/api/v" + self._api_version + "/" + + self._network_manager = QNetworkAccessManager() + self._network_manager.finished.connect(self._onNetworkRequestFinished) + # List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces # authentication requests. self._old_printers = [] @@ -28,6 +38,11 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self.removePrinterSignal.connect(self.removePrinter) Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) + # Get list of manual printers from preferences + preferences = Preferences.getInstance() + preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames + self._manual_instances = preferences.getValue("um3networkprinting/manual_instances").split(",") + addPrinterSignal = Signal() removePrinterSignal = Signal() printerListChanged = Signal() @@ -45,6 +60,28 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged]) + # Look for manual instances from preference + for address in self._manual_instances: + url = QUrl("http://" + address + self._api_prefix + "system/name") + + name_request = QNetworkRequest(url) + self._network_manager.get(name_request) + + ## Handler for all requests that have finished. + def _onNetworkRequestFinished(self, reply): + reply_url = reply.url().toString() + status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) + + if reply.operation() == QNetworkAccessManager.GetOperation: + if "system/name" in reply_url: # Name returned from printer. + if status_code == 200: + address = reply.url().host() + name = reply.readAll() + + instance_name = "manual:%s" % address + properties = { b"name": name.data() } + self.addPrinter(instance_name, address, properties) + ## Stop looking for devices on network. def stop(self): self._zero_conf.close() From 8dd8fd740c885eafb6ebdc3477cde2ce122cf99c Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 28 Sep 2016 23:05:53 +0200 Subject: [PATCH 262/297] Add UI for managing manually added printers CURA-2483 --- DiscoverUM3Action.qml | 98 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 8b20066ab2..4079df3f2e 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -66,13 +66,45 @@ Cura.MachineAction text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your Ultimaker 3, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your Ultimaker 3 from the list below:") } - Button + Row { - id: rediscoverButton - text: catalog.i18nc("@title", "Refresh") - onClicked: manager.restartDiscovery() - anchors.right: parent.right - anchors.rightMargin: parent.width * 0.5 + spacing: UM.Theme.getSize("default_lining").width + + Button + { + id: addButton + text: catalog.i18nc("@action:button", "Add"); + onClicked: + { + manualPrinterDialog.showDialog("", ""); + } + } + + Button + { + id: editButton + text: catalog.i18nc("@action:button", "Edit") + enabled: base.selectedPrinter && base.selectedPrinter.getKey().substr(0,7) =="manual:" + onClicked: + { + manualPrinterDialog.showDialog(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress); + } + } + + Button + { + id: removeButton + text: catalog.i18nc("@action:button", "Remove") + enabled: base.selectedPrinter && base.selectedPrinter.getKey().substr(0,7) =="manual:" + onClicked: manager.removeManualPrinter(base.selectedPrinter.getKey()) + } + + Button + { + id: rediscoverButton + text: catalog.i18nc("@title", "Refresh") + onClicked: manager.restartDiscovery() + } } Row @@ -226,4 +258,58 @@ Cura.MachineAction } } } + + UM.Dialog + { + id: manualPrinterDialog + property string printerKey + property alias addressText: addressField.text + + title: catalog.i18nc("@label", "IP Address") + + minimumWidth: 400 * Screen.devicePixelRatio + minimumHeight: 120 * Screen.devicePixelRatio + width: minimumWidth + height: minimumHeight + + signal showDialog(string key, string address) + onShowDialog: + { + printerKey = key; + + addressText = address; + addressField.selectAll(); + addressField.focus = true; + + manualPrinterDialog.show(); + } + + onAccepted: + { + manager.setManualPrinter(printerKey, addressText) + } + + Column { + anchors.fill: parent + + TextField { + id: addressField + width: parent.width + maximumLength: 40 + } + } + + rightButtons: [ + Button { + text: catalog.i18nc("@action:button","Cancel") + onClicked: manualPrinterDialog.reject() + }, + Button { + text: catalog.i18nc("@action:button", "Ok") + onClicked: manualPrinterDialog.accept() + enabled: manualPrinterDialog.addressText.trim() != "" + isDefault: true + } + ] + } } \ No newline at end of file From eba49ee8c287c6b63fbe82859fbf83aacbf5afda Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 28 Sep 2016 23:08:23 +0200 Subject: [PATCH 263/297] Always show manual printer instances, even before they are validated CURA-2483 --- NetworkPrinterOutputDevicePlugin.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index caf20c5629..f1d5acefef 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -62,10 +62,20 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): # Look for manual instances from preference for address in self._manual_instances: - url = QUrl("http://" + address + self._api_prefix + "system/name") + self.addManualPrinter(address) - name_request = QNetworkRequest(url) - self._network_manager.get(name_request) + def addManualPrinter(self, address): + # Add a preliminary printer instance + name = address + instance_name = "manual:%s" % address + properties = { b"name": name.encode("UTF-8") } + self.addPrinter(instance_name, address, properties) + + # Check if a printer exists at this address + # If a printer responds, it will replace the preliminary printer created above + url = QUrl("http://" + address + self._api_prefix + "system/name") + name_request = QNetworkRequest(url) + self._network_manager.get(name_request) ## Handler for all requests that have finished. def _onNetworkRequestFinished(self, reply): @@ -76,10 +86,12 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): if "system/name" in reply_url: # Name returned from printer. if status_code == 200: address = reply.url().host() - name = reply.readAll() + name = reply.readAll().data().decode() + name = ("%s (%s)" % (name, address)) instance_name = "manual:%s" % address - properties = { b"name": name.data() } + properties = { b"name": name.encode("UTF-8") } + self.removePrinter(instance_name) self.addPrinter(instance_name, address, properties) ## Stop looking for devices on network. From 578b4d3826fa8f886553561381f65f709a3d95d8 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 29 Sep 2016 08:47:29 +0200 Subject: [PATCH 264/297] Implement Adding, Editing and Removing manual printers CURA-2384 --- DiscoverUM3Action.py | 16 +++++++++++++++ DiscoverUM3Action.qml | 2 +- NetworkPrinterOutputDevicePlugin.py | 32 ++++++++++++++++++++++------- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index cda6ea386a..35f4e48c71 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -52,6 +52,22 @@ class DiscoverUM3Action(MachineAction): else: self._network_plugin.startDiscovery() + @pyqtSlot(str, str) + def removeManualPrinter(self, key, address): + if not self._network_plugin: + return + + self._network_plugin.removeManualPrinter(key, address) + + @pyqtSlot(str, str) + def setManualPrinter(self, key, address): + if key != "": + # This manual printer replaces a current manual printer + self._network_plugin.removeManualPrinter(key) + + if address != "": + self._network_plugin.addManualPrinter(address) + def _onPrinterDiscoveryChanged(self, *args): self._last_zeroconf_event_time = time.time() self.printersChanged.emit() diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 4079df3f2e..0cd4ba0481 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -96,7 +96,7 @@ Cura.MachineAction id: removeButton text: catalog.i18nc("@action:button", "Remove") enabled: base.selectedPrinter && base.selectedPrinter.getKey().substr(0,7) =="manual:" - onClicked: manager.removeManualPrinter(base.selectedPrinter.getKey()) + onClicked: manager.removeManualPrinter(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress) } Button diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index f1d5acefef..83416c4fc6 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -39,9 +39,9 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) # Get list of manual printers from preferences - preferences = Preferences.getInstance() - preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames - self._manual_instances = preferences.getValue("um3networkprinting/manual_instances").split(",") + self._preferences = Preferences.getInstance() + self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames + self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",") addPrinterSignal = Signal() removePrinterSignal = Signal() @@ -65,11 +65,17 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self.addManualPrinter(address) def addManualPrinter(self, address): - # Add a preliminary printer instance + if address not in self._manual_instances: + self._manual_instances.append(address) + self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances)) + name = address instance_name = "manual:%s" % address properties = { b"name": name.encode("UTF-8") } - self.addPrinter(instance_name, address, properties) + + if instance_name not in self._printers: + # Add a preliminary printer instance + self.addPrinter(instance_name, address, properties) # Check if a printer exists at this address # If a printer responds, it will replace the preliminary printer created above @@ -77,6 +83,16 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): name_request = QNetworkRequest(url) self._network_manager.get(name_request) + def removeManualPrinter(self, key, address = None): + if key in self._printers: + if not address: + address = self._printers[key].ipAddress + self.removePrinter(key) + + if address in self._manual_instances: + self._manual_instances.remove(address) + self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances)) + ## Handler for all requests that have finished. def _onNetworkRequestFinished(self, reply): reply_url = reply.url().toString() @@ -91,8 +107,10 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): instance_name = "manual:%s" % address properties = { b"name": name.encode("UTF-8") } - self.removePrinter(instance_name) - self.addPrinter(instance_name, address, properties) + if instance_name in self._printers: + # Only replace the printer if it is still in the list of (manual) printers + self.removePrinter(instance_name) + self.addPrinter(instance_name, address, properties) ## Stop looking for devices on network. def stop(self): From a2722c757173dbb48b7c10012f49803efad3e2fc Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 29 Sep 2016 09:41:21 +0200 Subject: [PATCH 265/297] Get firmware version through API... ...for parity between discovered printers and manual printers CURA-2384 --- NetworkPrinterOutputDevicePlugin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 83416c4fc6..96c43d5d0d 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -11,6 +11,7 @@ from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, QNetworkRepl from PyQt5.QtCore import QUrl import time +import json ## This plugin handles the connection detection & creation of output device objects for the UM3 printer. # Zero-Conf is used to detect printers, which are saved in a dict. @@ -79,7 +80,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): # Check if a printer exists at this address # If a printer responds, it will replace the preliminary printer created above - url = QUrl("http://" + address + self._api_prefix + "system/name") + url = QUrl("http://" + address + self._api_prefix + "system") name_request = QNetworkRequest(url) self._network_manager.get(name_request) @@ -99,14 +100,14 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if reply.operation() == QNetworkAccessManager.GetOperation: - if "system/name" in reply_url: # Name returned from printer. + if "system" in reply_url: # Name returned from printer. if status_code == 200: + system_info = json.loads(bytes(reply.readAll()).decode("utf-8")) address = reply.url().host() - name = reply.readAll().data().decode() - name = ("%s (%s)" % (name, address)) + name = ("%s (%s)" % (system_info["name"], address)) instance_name = "manual:%s" % address - properties = { b"name": name.encode("UTF-8") } + properties = { b"name": name.encode("UTF-8"), b"firmware_version": system_info["firmware"].encode("UTF-8") } if instance_name in self._printers: # Only replace the printer if it is still in the list of (manual) printers self.removePrinter(instance_name) From ea9ba87fa4e96924d1b55ba761823e38a0b29541 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 29 Sep 2016 14:29:33 +0200 Subject: [PATCH 266/297] Only allow connecting if the printer has responded to API query CURA-2483 --- DiscoverUM3Action.qml | 14 ++++++++++---- NetworkPrinterOutputDevicePlugin.py | 18 +++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 0cd4ba0481..4daeaa1c37 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -11,6 +11,7 @@ Cura.MachineAction id: base anchors.fill: parent; property var selectedPrinter: null + property bool completeProperties: true property var connectingToPrinter: null Connections @@ -84,7 +85,7 @@ Cura.MachineAction { id: editButton text: catalog.i18nc("@action:button", "Edit") - enabled: base.selectedPrinter && base.selectedPrinter.getKey().substr(0,7) =="manual:" + enabled: base.selectedPrinter != null && (base.selectedPrinter.getKey().substr(0,7) =="manual:") onClicked: { manualPrinterDialog.showDialog(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress); @@ -95,7 +96,7 @@ Cura.MachineAction { id: removeButton text: catalog.i18nc("@action:button", "Remove") - enabled: base.selectedPrinter && base.selectedPrinter.getKey().substr(0,7) =="manual:" + enabled: base.selectedPrinter != null && (base.selectedPrinter.getKey().substr(0,7) =="manual:") onClicked: manager.removeManualPrinter(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress) } @@ -150,7 +151,12 @@ Cura.MachineAction } width: parent.width currentIndex: -1 - onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex] + onCurrentIndexChanged: + { + base.selectedPrinter = listview.model[currentIndex]; + // Only allow connecting if the printer has responded to API query since the last refresh + base.completeProperties = base.selectedPrinter != null && (base.selectedPrinter.firmwareVersion != ""); + } Component.onCompleted: manager.startDiscovery() delegate: Rectangle { @@ -252,7 +258,7 @@ Cura.MachineAction Button { text: catalog.i18nc("@action:button", "Connect") - enabled: base.selectedPrinter ? true : false + enabled: (base.selectedPrinter && base.completeProperties) ? true : false onClicked: connectToPrinter() } } diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 96c43d5d0d..4843ab8975 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -63,7 +63,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): # Look for manual instances from preference for address in self._manual_instances: - self.addManualPrinter(address) + if address: + self.addManualPrinter(address) def addManualPrinter(self, address): if address not in self._manual_instances: @@ -72,17 +73,13 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): name = address instance_name = "manual:%s" % address - properties = { b"name": name.encode("UTF-8") } + properties = { b"name": name.encode("UTF-8"), b"incomplete": True } if instance_name not in self._printers: # Add a preliminary printer instance self.addPrinter(instance_name, address, properties) - # Check if a printer exists at this address - # If a printer responds, it will replace the preliminary printer created above - url = QUrl("http://" + address + self._api_prefix + "system") - name_request = QNetworkRequest(url) - self._network_manager.get(name_request) + self.checkManualPrinter(address) def removeManualPrinter(self, key, address = None): if key in self._printers: @@ -94,6 +91,13 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self._manual_instances.remove(address) self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances)) + def checkManualPrinter(self, address): + # Check if a printer exists at this address + # If a printer responds, it will replace the preliminary printer created above + url = QUrl("http://" + address + self._api_prefix + "system") + name_request = QNetworkRequest(url) + self._network_manager.get(name_request) + ## Handler for all requests that have finished. def _onNetworkRequestFinished(self, reply): reply_url = reply.url().toString() From b25b92a81bce184b406e3ebdc7feef3072a25e32 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 29 Sep 2016 14:46:31 +0200 Subject: [PATCH 267/297] Don't allow spaces, commas and other illegal chars in the address CURA-2384 --- DiscoverUM3Action.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 4daeaa1c37..5bac7d89ba 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -302,6 +302,10 @@ Cura.MachineAction id: addressField width: parent.width maximumLength: 40 + validator: RegExpValidator + { + regExp: /[a-zA-Z0-9\.\-\_]*/ + } } } From 042ebe76ba4c240fcc8bdb1753cb780363f5fbb5 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 29 Sep 2016 15:32:42 +0200 Subject: [PATCH 268/297] Don't rely on key prefix, remove code duplication (_api_prefix) CURA-2384 --- DiscoverUM3Action.qml | 6 +++--- NetworkPrinterOutputDevice.py | 13 ++++++++++--- NetworkPrinterOutputDevicePlugin.py | 6 +++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 5bac7d89ba..159d72a821 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -85,7 +85,7 @@ Cura.MachineAction { id: editButton text: catalog.i18nc("@action:button", "Edit") - enabled: base.selectedPrinter != null && (base.selectedPrinter.getKey().substr(0,7) =="manual:") + enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true" onClicked: { manualPrinterDialog.showDialog(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress); @@ -96,7 +96,7 @@ Cura.MachineAction { id: removeButton text: catalog.i18nc("@action:button", "Remove") - enabled: base.selectedPrinter != null && (base.selectedPrinter.getKey().substr(0,7) =="manual:") + enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true" onClicked: manager.removeManualPrinter(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress) } @@ -155,7 +155,7 @@ Cura.MachineAction { base.selectedPrinter = listview.model[currentIndex]; // Only allow connecting if the printer has responded to API query since the last refresh - base.completeProperties = base.selectedPrinter != null && (base.selectedPrinter.firmwareVersion != ""); + base.completeProperties = base.selectedPrinter != null && base.selectedPrinter.getProperty("incomplete") != "true"; } Component.onCompleted: manager.startDiscovery() delegate: Rectangle diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e2fe36f118..e3eb15f859 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -36,11 +36,12 @@ class AuthState(IntEnum): ## Network connected (wifi / lan) printer that uses the Ultimaker API @signalemitter class NetworkPrinterOutputDevice(PrinterOutputDevice): - def __init__(self, key, address, properties): + def __init__(self, key, address, properties, api_prefix): super().__init__(key) self._address = address self._key = key self._properties = properties # Properties dict as provided by zero conf + self._api_prefix = api_prefix self._gcode = None self._print_finished = True # _print_finsihed == False means we're halfway in a print @@ -94,8 +95,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._material_ids = [""] * self._num_extruders self._hotend_ids = [""] * self._num_extruders - self._api_version = "1" - self._api_prefix = "/api/v" + self._api_version + "/" self.setPriority(2) # Make sure the output device gets selected above local file output self.setName(key) self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print over network")) @@ -187,6 +186,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def getProperties(self): return self._properties + @pyqtSlot(str, result = str) + def getProperty(self, key): + key = key.encode("utf-8") + if key in self._properties: + return self._properties.get(key, b"").decode("utf-8") + else: + return "" + ## Get the unique key of this machine # \return key String containing the key of the machine. @pyqtSlot(result = str) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 4843ab8975..8dc3cd8646 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -73,7 +73,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): name = address instance_name = "manual:%s" % address - properties = { b"name": name.encode("UTF-8"), b"incomplete": True } + properties = { b"name": name.encode("utf-8"), b"manual": b"true", b"incomplete": b"true" } if instance_name not in self._printers: # Add a preliminary printer instance @@ -111,7 +111,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): name = ("%s (%s)" % (system_info["name"], address)) instance_name = "manual:%s" % address - properties = { b"name": name.encode("UTF-8"), b"firmware_version": system_info["firmware"].encode("UTF-8") } + properties = { b"name": name.encode("utf-8"), b"firmware_version": system_info["firmware"].encode("utf-8"), b"manual": b"true" } if instance_name in self._printers: # Only replace the printer if it is still in the list of (manual) printers self.removePrinter(instance_name) @@ -139,7 +139,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addPrinter(self, name, address, properties): - printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties) + printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties, self._api_prefix) self._printers[printer.getKey()] = printer global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"): From 4f9eeb6be649dc9c1a7a7f01602d4112464e46de Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sat, 1 Oct 2016 13:06:38 +0200 Subject: [PATCH 269/297] Hide printer details until printer has divulged them CURA-2483 --- DiscoverUM3Action.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 159d72a821..bb00588cb3 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -215,6 +215,7 @@ Cura.MachineAction } Grid { + visible: base.completeProperties width: parent.width columns: 2 Label From 8e5dbb78a4f00a66294798de811a48e147aa55ea Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sat, 1 Oct 2016 13:07:55 +0200 Subject: [PATCH 270/297] Don't connect to a printer if we don't have its properties yet CURA-2483 --- DiscoverUM3Action.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index bb00588cb3..a6ba96ab2a 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -30,7 +30,7 @@ Cura.MachineAction function connectToPrinter() { - if(base.selectedPrinter) + if(base.selectedPrinter && base.completeProperties) { var printerKey = base.selectedPrinter.getKey() if(connectingToPrinter != printerKey) { From e6570884fc54a4e440c5b99f89f9b39eda586ab4 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sat, 1 Oct 2016 13:16:14 +0200 Subject: [PATCH 271/297] Hide dialog when accepting/rejecting Under some circumstances, UM.Dialog would not hide the dialog, so we do it ourselves. CURA-2384 --- DiscoverUM3Action.qml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index a6ba96ab2a..d583842dcf 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -313,11 +313,19 @@ Cura.MachineAction rightButtons: [ Button { text: catalog.i18nc("@action:button","Cancel") - onClicked: manualPrinterDialog.reject() + onClicked: + { + manualPrinterDialog.reject() + manualPrinterDialog.hide() + } }, Button { text: catalog.i18nc("@action:button", "Ok") - onClicked: manualPrinterDialog.accept() + onClicked: + { + manualPrinterDialog.accept() + manualPrinterDialog.hide() + } enabled: manualPrinterDialog.addressText.trim() != "" isDefault: true } From 8a6d1254786cfe7647195b809dc2071f08853697 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 3 Oct 2016 12:29:50 +0200 Subject: [PATCH 272/297] Add strings to clarify the manual printer UI Also adds resuming/pausing states strings and a "Print Again" to use elsewhere. --- DiscoverUM3Action.qml | 39 ++++++++++++++++++++++++++++------- NetworkPrinterOutputDevice.py | 7 +++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index d583842dcf..0cc6031e7f 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -54,7 +54,7 @@ Cura.MachineAction { id: pageTitle width: parent.width - text: catalog.i18nc("@title", "Connect to Networked Printer") + text: catalog.i18nc("@title:window", "Connect to Networked Printer") wrapMode: Text.WordWrap font.pointSize: 18 } @@ -64,7 +64,7 @@ Cura.MachineAction id: pageDescription width: parent.width wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your Ultimaker 3, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your Ultimaker 3 from the list below:") + text: catalog.i18nc("@label", "To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your printer from the list below:") } Row @@ -103,7 +103,7 @@ Cura.MachineAction Button { id: rediscoverButton - text: catalog.i18nc("@title", "Refresh") + text: catalog.i18nc("@action:button", "Refresh") onClicked: manager.restartDiscovery() } } @@ -195,7 +195,7 @@ Cura.MachineAction wrapMode: Text.WordWrap //: Tips label //TODO: get actual link from webteam - text: catalog.i18nc("@label", "If your Ultimaker 3 is not listed, read the Ultimaker 3 network troubleshooting guide").arg("https://ultimaker.com/en/troubleshooting"); + text: catalog.i18nc("@label", "If your printer is not listed, read the network-printing troubleshooting guide").arg("https://ultimaker.com/en/troubleshooting"); onLinkActivated: Qt.openUrlExternally(link) } @@ -228,7 +228,7 @@ Cura.MachineAction { width: parent.width * 0.5 wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Ultimaker 3") + text: true ? catalog.i18nc("@label", "Ultimaker 3") : catalog.i18nc("@label", "Ultimaker 3 Extended") } Label { @@ -246,7 +246,7 @@ Cura.MachineAction { width: parent.width * 0.5 wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "IP Address") + text: catalog.i18nc("@label", "Address") } Label { @@ -255,6 +255,13 @@ Cura.MachineAction text: base.selectedPrinter ? base.selectedPrinter.ipAddress : "" } } + Label + { + width: parent.width + wrapMode: Text.WordWrap + visible: base.selectedPrinter && !base.completeProperties + text: catalog.i18nc("@label", "The printer at this address has not yet responded." ) + } Button { @@ -266,13 +273,20 @@ Cura.MachineAction } } + Label + { + // TODO: move use this in an appropriate location + visible: false + text: catalog.i18nc("@label:", "Print Again") + } + UM.Dialog { id: manualPrinterDialog property string printerKey property alias addressText: addressField.text - title: catalog.i18nc("@label", "IP Address") + title: catalog.i18nc("@title:window", "Printer Address") minimumWidth: 400 * Screen.devicePixelRatio minimumHeight: 120 * Screen.devicePixelRatio @@ -298,8 +312,17 @@ Cura.MachineAction Column { anchors.fill: parent + spacing: UM.Theme.getSize("default_margin").height - TextField { + Label + { + text: catalog.i18nc("@alabel","Enter the IP address or hostname of your printer on the network.") + width: parent.width + wrapMode: Text.WordWrap + } + + TextField + { id: addressField width: parent.width maximumLength: 40 diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index e3eb15f859..288d01f975 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -804,6 +804,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Print aborted. Please check the printer")) state = "error" + # NB/TODO: the following two states are intentionally added for future proofing the i18n strings + # but are currently non-functional + if state == "!pausing": + self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Pausing print...")) + if state == "!resuming": + self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Resuming print...")) + self._updateJobState(state) self.setTimeElapsed(json_data["time_elapsed"]) self.setTimeTotal(json_data["time_total"]) From a31a4a1e90dd4aaf0f445abc359b3ad4c2875a86 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Oct 2016 13:49:01 +0200 Subject: [PATCH 273/297] Reinitialise Zeroconf network socket upon refresh/start Reinitialising Zeroconf entirely causes CPU usage to go through the proverbial roof. This is sort of a hack, since we're touching the _listen_socket variable inside Zeroconf, which it hasn't exposed. But it works to still be able to refresh Zeroconf after network switches, and to not have high CPU usage afterwards. Contributes to issue CURA-2497. --- NetworkPrinterOutputDevicePlugin.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index bb1fade0bc..29f33a04de 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -1,7 +1,7 @@ from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from . import NetworkPrinterOutputDevice -from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo +from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo, new_socket from UM.Logger import Logger from UM.Signal import Signal, signalemitter from UM.Application import Application @@ -20,7 +20,7 @@ import json class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): def __init__(self): super().__init__() - self._zero_conf = None + self._zero_conf = Zeroconf() self._browser = None self._printers = {} @@ -53,16 +53,18 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self.startDiscovery() def startDiscovery(self): - self.stop() if self._browser: self._browser.cancel() self._browser = None self._old_printers = [printer_name for printer_name in self._printers] self._printers = {} self.printerListChanged.emit() - # After network switching, one must make a new instance of Zeroconf - # On windows, the instance creation is very fast (unnoticable). Other platforms? - self._zero_conf = Zeroconf() + #After network switching, Zeroconf's network socket is no longer functional. + #Zeroconf must reinitialise its socket, but reinitialising Zeroconf causes massive CPU usage. + #So we only reinitialise Zeroconf's listening socket. + self._zero_conf.engine.del_reader(self._zero_conf._listen_socket) + self._zero_conf._listen_socket = new_socket() #Warning: Touching Zeroconf's privates! It has no functionality to reinitialise its own socket. + self._zero_conf.engine.add_reader(self._zero_conf.listener, self._zero_conf._listen_socket) self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged]) # Look for manual instances from preference @@ -123,8 +125,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): ## Stop looking for devices on network. def stop(self): - if self._zero_conf is not None: - self._zero_conf.close() + self._zero_conf.close() def getPrinters(self): return self._printers From 8bd3b7ea9b140340d4c69bff60f09101563a764e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Oct 2016 14:50:08 +0200 Subject: [PATCH 274/297] Revert "Reinitialise Zeroconf network socket upon refresh/start" This reverts commit a31a4a1e90dd4aaf0f445abc359b3ad4c2875a86. --- NetworkPrinterOutputDevicePlugin.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/NetworkPrinterOutputDevicePlugin.py b/NetworkPrinterOutputDevicePlugin.py index 29f33a04de..bb1fade0bc 100644 --- a/NetworkPrinterOutputDevicePlugin.py +++ b/NetworkPrinterOutputDevicePlugin.py @@ -1,7 +1,7 @@ from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from . import NetworkPrinterOutputDevice -from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo, new_socket +from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo from UM.Logger import Logger from UM.Signal import Signal, signalemitter from UM.Application import Application @@ -20,7 +20,7 @@ import json class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): def __init__(self): super().__init__() - self._zero_conf = Zeroconf() + self._zero_conf = None self._browser = None self._printers = {} @@ -53,18 +53,16 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): self.startDiscovery() def startDiscovery(self): + self.stop() if self._browser: self._browser.cancel() self._browser = None self._old_printers = [printer_name for printer_name in self._printers] self._printers = {} self.printerListChanged.emit() - #After network switching, Zeroconf's network socket is no longer functional. - #Zeroconf must reinitialise its socket, but reinitialising Zeroconf causes massive CPU usage. - #So we only reinitialise Zeroconf's listening socket. - self._zero_conf.engine.del_reader(self._zero_conf._listen_socket) - self._zero_conf._listen_socket = new_socket() #Warning: Touching Zeroconf's privates! It has no functionality to reinitialise its own socket. - self._zero_conf.engine.add_reader(self._zero_conf.listener, self._zero_conf._listen_socket) + # After network switching, one must make a new instance of Zeroconf + # On windows, the instance creation is very fast (unnoticable). Other platforms? + self._zero_conf = Zeroconf() self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged]) # Look for manual instances from preference @@ -125,7 +123,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin): ## Stop looking for devices on network. def stop(self): - self._zero_conf.close() + if self._zero_conf is not None: + self._zero_conf.close() def getPrinters(self): return self._printers From 029fda72e340850526a2dc58af56c49250fbb0e8 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 4 Oct 2016 19:09:10 +0200 Subject: [PATCH 275/297] Fix warning when there is no current printer --- DiscoverUM3Action.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 0cc6031e7f..79f52460de 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -259,7 +259,7 @@ Cura.MachineAction { width: parent.width wrapMode: Text.WordWrap - visible: base.selectedPrinter && !base.completeProperties + visible: base.selectedPrinter != null && !base.completeProperties text: catalog.i18nc("@label", "The printer at this address has not yet responded." ) } From 8cbd6443be6dee070a225d2e444fa3080204c0e2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 5 Oct 2016 14:31:41 +0200 Subject: [PATCH 276/297] UM3 familiy printers now set their machine type CURA-2475 --- NetworkPrinterOutputDevice.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 288d01f975..f57637af1f 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -166,6 +166,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._compressing_print = False + printer_type = self._properties.get(b"machine", b"").decode("utf-8") + if printer_type == "9511.0": + self._updatePrinterType("UM3Extended") + elif printer_type == "9066.0": + self._updatePrinterType("UM3") + else: + self._updatePrinterType("unknown") + def _onNetworkAccesibleChanged(self, accessible): Logger.log("d", "Network accessible state changed to: %s", accessible) From ec63d6931edbebdab5f3d9a5a435f46dee6c13f5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 5 Oct 2016 14:54:22 +0200 Subject: [PATCH 277/297] Actually perform the filtering based on machine type CURA-2475 --- DiscoverUM3Action.py | 3 +++ NetworkPrinterOutputDevice.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 3d69a8c118..8213812391 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -75,7 +75,10 @@ class DiscoverUM3Action(MachineAction): @pyqtProperty("QVariantList", notify = printersChanged) def foundDevices(self): if self._network_plugin: + global_printer_type = Application.getInstance().getGlobalContainerStack().getBottom().getId() printers = list(self._network_plugin.getPrinters().values()) + # TODO; There are still some testing printers that don't have a correct printer type, so don't filter out unkown ones just yet. + printers = [printer for printer in printers if printer.printerType == global_printer_type or printer.printerType == "unknown"] printers.sort(key = lambda k: k.name) return printers else: diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f57637af1f..3d56a777aa 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -168,9 +168,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): printer_type = self._properties.get(b"machine", b"").decode("utf-8") if printer_type == "9511.0": - self._updatePrinterType("UM3Extended") + self._updatePrinterType("ultimaker3_extended") elif printer_type == "9066.0": - self._updatePrinterType("UM3") + self._updatePrinterType("ultimaker3") else: self._updatePrinterType("unknown") From f71b23c72aadf3b179e81d5fc691da164b8af1e7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 6 Oct 2016 09:35:31 +0200 Subject: [PATCH 278/297] Added handling for if there is no global stack CURA-2475 --- DiscoverUM3Action.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 8213812391..7d7484f7af 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -75,7 +75,11 @@ class DiscoverUM3Action(MachineAction): @pyqtProperty("QVariantList", notify = printersChanged) def foundDevices(self): if self._network_plugin: - global_printer_type = Application.getInstance().getGlobalContainerStack().getBottom().getId() + if Application.getInstance().getGlobalContainerStack(): + global_printer_type = Application.getInstance().getGlobalContainerStack().getBottom().getId() + else: + global_printer_type = "unknown" + printers = list(self._network_plugin.getPrinters().values()) # TODO; There are still some testing printers that don't have a correct printer type, so don't filter out unkown ones just yet. printers = [printer for printer in printers if printer.printerType == global_printer_type or printer.printerType == "unknown"] From 2688928467d602d808cbe2d5efaddae9bd98a946 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 6 Oct 2016 11:30:56 +0200 Subject: [PATCH 279/297] Revision number is no longer taken into account CURA-2475 --- DiscoverUM3Action.py | 2 +- NetworkPrinterOutputDevice.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index 7d7484f7af..c192d36ac1 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -79,7 +79,7 @@ class DiscoverUM3Action(MachineAction): global_printer_type = Application.getInstance().getGlobalContainerStack().getBottom().getId() else: global_printer_type = "unknown" - + printers = list(self._network_plugin.getPrinters().values()) # TODO; There are still some testing printers that don't have a correct printer type, so don't filter out unkown ones just yet. printers = [printer for printer in printers if printer.printerType == global_printer_type or printer.printerType == "unknown"] diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 3d56a777aa..2eb126d966 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -167,9 +167,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._compressing_print = False printer_type = self._properties.get(b"machine", b"").decode("utf-8") - if printer_type == "9511.0": + if printer_type.startswith("9511"): self._updatePrinterType("ultimaker3_extended") - elif printer_type == "9066.0": + elif printer_type.startswith("9066"): self._updatePrinterType("ultimaker3") else: self._updatePrinterType("unknown") From 93891490647952219ba75e6a698137b753dcd7a8 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 6 Oct 2016 11:36:26 +0200 Subject: [PATCH 280/297] Type name display is now also updated in print discovery CURA-2475 --- DiscoverUM3Action.qml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 79f52460de..06b701605e 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -228,7 +228,26 @@ Cura.MachineAction { width: parent.width * 0.5 wrapMode: Text.WordWrap - text: true ? catalog.i18nc("@label", "Ultimaker 3") : catalog.i18nc("@label", "Ultimaker 3 Extended") + text: + { + if(base.selectedPrinter) + { + if(base.selectedPrinter.printerType == "ultimaker3") + { + return catalog.i18nc("@label", "Ultimaker 3") + } else if(base.selectedPrinter.printerType == "ultimaker3_extended") + { + return catalog.i18nc("@label", "Ultimaker 3 Extended") + } else + { + return catalog.i18nc("@label", "Unknown") // We have no idea what type it is. Should not happen 'in the field' + } + } + else + { + return "" + } + } } Label { From afd5df283c5f09dc821dfde4bdc9c0549f76e95f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 6 Oct 2016 14:33:42 +0200 Subject: [PATCH 281/297] Re-filter list of printers when you open the window When you open the window of a printer that already exists, it doesn't re-create the entire window and therefore didn't re-apply the filter on machine type. This triggers the filter to be applied again. Contributes to issue CURA-2475. --- DiscoverUM3Action.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DiscoverUM3Action.py b/DiscoverUM3Action.py index c192d36ac1..c4ffdb8472 100644 --- a/DiscoverUM3Action.py +++ b/DiscoverUM3Action.py @@ -39,6 +39,11 @@ class DiscoverUM3Action(MachineAction): self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged) self.printersChanged.emit() + ## Re-filters the list of printers. + @pyqtSlot() + def reset(self): + self.printersChanged.emit() + @pyqtSlot() def restartDiscovery(self): # Ensure that there is a bit of time after a printer has been discovered. From 26383658390012425c1982cf88b2a573fe359f2c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 12 Oct 2016 14:36:41 +0200 Subject: [PATCH 282/297] LastRequestTime is reset on connection close CURA-2630 --- NetworkPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2eb126d966..a1bb52c514 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -464,6 +464,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Reset timeout state self._connection_state_before_timeout = None self._last_response_time = time() + self._last_request_time = None # Stop update timers self._update_timer.stop() From a9b45572cc0343ee3f83353bf92d3cacc7dfcfb7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 12 Oct 2016 14:50:58 +0200 Subject: [PATCH 283/297] PostReply is now always reset correctly CURA-2630 --- NetworkPrinterOutputDevice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index a1bb52c514..2fd433775f 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -631,6 +631,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._compressing_print = False if self._post_reply: self._post_reply.abort() + self._post_reply = None Application.getInstance().showPrintMonitor.emit(False) ## Attempt to start a new print. @@ -747,6 +748,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._post_reply.abort() self._post_reply.uploadProgress.disconnect(self._onUploadProgress) Logger.log("d", "Uploading of print failed after %s", time() - self._send_gcode_start) + self._post_reply = None self._progress_message.hide() self.setConnectionState(ConnectionState.error) @@ -901,6 +903,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): elif "print_job" in reply_url: reply.uploadProgress.disconnect(self._onUploadProgress) Logger.log("d", "Uploading of print succeeded after %s", time() - self._send_gcode_start) + # Only reset the _post_reply if it was the same one. + if reply == self._post_reply: + self._post_reply = None self._progress_message.hide() elif reply.operation() == QNetworkAccessManager.PutOperation: From 2337a78a71e36386a687f8e8a7f0be0703ea45ba Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 12 Oct 2016 15:08:23 +0200 Subject: [PATCH 284/297] Camera requests now also set last_request_time CURA-2630 --- NetworkPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2fd433775f..934092f33c 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -230,6 +230,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): url = QUrl("http://" + self._address + ":8080/?action=snapshot") image_request = QNetworkRequest(url) self._manager.get(image_request) + self._last_request_time = time() ## Set the authentication state. # \param auth_state \type{AuthState} Enum value representing the new auth state From 91521eb49d1e6c0664fdc98b944d0e09e961535b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 12 Oct 2016 16:02:35 +0200 Subject: [PATCH 285/297] Added logging if connection to network printer was closed CURA-2630 --- NetworkPrinterOutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 934092f33c..2581a4c3ca 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -440,6 +440,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def close(self): + Logger.log("d", "Closing connection of printer %s with ip %s", self._key, self._address) self._updateJobState("") self.setConnectionState(ConnectionState.closed) if self._progress_message: From dc34b898d46da7af94ac2ad368fac7d9c0c70249 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 12 Oct 2016 16:35:22 +0200 Subject: [PATCH 286/297] Added max recreateNetworkManager count increase per update CURA-2630 --- NetworkPrinterOutputDevice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 2581a4c3ca..ac3af93c84 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -314,9 +314,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._last_response_time and self._connection_state_before_timeout: if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count: self._recreate_network_manager_count += 1 + counter = 0 # Counter to prevent possible indefinite while loop. # It can happen that we had a very long timeout (multiple times the recreate time). # In that case we should jump through the point that the next update won't be right away. - while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time: + while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time and counter < 10: + counter += 1 self._recreate_network_manager_count += 1 Logger.log("d", "Timeout lasted over 30 seconds (%.1fs), re-checking connection.", time_since_last_response) self._createNetworkManager() From 8951efd140fefd7af5d5e98d381558af5a291f7a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 13 Oct 2016 11:26:10 +0200 Subject: [PATCH 287/297] Improve log message Instead of always reporting that it waits 30s, it reports the actual time it waits. Contributes to issue CURA-2630. --- NetworkPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index ac3af93c84..619a70a952 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -320,7 +320,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time and counter < 10: counter += 1 self._recreate_network_manager_count += 1 - Logger.log("d", "Timeout lasted over 30 seconds (%.1fs), re-checking connection.", time_since_last_response) + Logger.log("d", "Timeout lasted over %.0f seconds (%.1fs), re-checking connection.", self._recreate_network_manager_time, time_since_last_response) self._createNetworkManager() return @@ -495,7 +495,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): print_information = Application.getInstance().getPrintInformation() - # Check if PrintCores / materials are loaded at all. Any failure in these results in an Error. + # Check if print cores / materials are loaded at all. Any failure in these results in an error. for index in range(0, self._num_extruders): if print_information.materialLengths[index] != 0: if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "": From 4cc6fb08780f8471938e9b92469ddf80c6cbb70b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 13 Oct 2016 18:53:22 +0200 Subject: [PATCH 288/297] Remove unused label Cleanup before release --- DiscoverUM3Action.qml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/DiscoverUM3Action.qml b/DiscoverUM3Action.qml index 79f52460de..219ec25f25 100644 --- a/DiscoverUM3Action.qml +++ b/DiscoverUM3Action.qml @@ -273,13 +273,6 @@ Cura.MachineAction } } - Label - { - // TODO: move use this in an appropriate location - visible: false - text: catalog.i18nc("@label:", "Print Again") - } - UM.Dialog { id: manualPrinterDialog From be356bae3744d0787adf02dd35552ad8b91863b4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 17 Oct 2016 15:52:55 +0200 Subject: [PATCH 289/297] Allow for writing multiple meshes at the same time This is an API break in Uranium. It has no effect on this plug-in since this plug-in always writes the entire g-code anyway. Contributes to issue CURA-2617. --- NetworkPrinterOutputDevice.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 619a70a952..b59896123e 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -1,3 +1,6 @@ +# Copyright (c) 2016 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + from UM.i18n import i18nCatalog from UM.Application import Application from UM.Logger import Logger @@ -474,7 +477,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.stop() self._camera_timer.stop() - def requestWrite(self, node, file_name = None, filter_by_machine = False): + ## Request the current scene to be sent to a network-connected printer. + # + # \param nodes A collection of scene nodes to send. This is ignored. + # \param file_name \type{string} A suggestion for a file name to write. + # This is ignored. + # \param filter_by_machine Whether to filter MIME types by machine. This + # is ignored. + def requestWrite(self, nodes, file_name = None, filter_by_machine = False): if self._progress != 0: self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to start a new print job because the printer is busy. Please check the printer.")) self._error_message.show() From 7427730123bff19d30c97795b8e7c2e533ea1613 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 18 Oct 2016 12:56:15 +0200 Subject: [PATCH 290/297] Update question to sync printer configuration UX designer wanted to have the text changed. Contributes to issue CURA-2634. --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index b59896123e..9419eeaf16 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -947,7 +947,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def materialHotendChangedMessage(self, callback): Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Changes on the Printer"), i18n_catalog.i18nc("@label", - "Do you want to change the PrintCores and materials in Cura to match your printer?"), + "Would you like to update your current printer configuration into Cura?"), i18n_catalog.i18nc("@label", "The PrintCores and/or materials on your printer were changed. For the best result, always slice for the PrintCores and materials that are inserted in your printer."), buttons=QMessageBox.Yes + QMessageBox.No, From b1f3280b2da711a61f4696465c12e8b47747a2e8 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 19 Oct 2016 13:03:23 +0200 Subject: [PATCH 291/297] No longer say this is a secret plug-in It is no longer secret since the UM3 is released. Contributes to issue CURA-2737. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e88ce593b..54e3ff5eae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # UM3NetworkPrintingPlugin -Secret plugin to enable wifi printing from Cura to UM3 +Plugin to enable wifi printing from Cura to UM3. Intructions ---- From 601bf20a962fbc5989daf50bfbc20124b0aef6d9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 26 Oct 2016 13:38:09 +0200 Subject: [PATCH 292/297] Changed the entry point of the material guid to non-depricated spot We depricated the GUID as entry point, but all printers support both right now. --- NetworkPrinterOutputDevice.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 619a70a952..f674930dc8 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -68,12 +68,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # "extruders": [ # { # "feeder": {"max_speed": 45.0, "jerk": 5.0, "acceleration": 3000.0}, - # "active_material": {"GUID": "xxxxxxx", "length_remaining": -1.0}, + # "active_material": {"guid": "xxxxxxx", "length_remaining": -1.0}, # "hotend": {"temperature": {"target": 0.0, "current": 22.8}, "id": "AA 0.4"} # }, # { # "feeder": {"max_speed": 45.0, "jerk": 5.0, "acceleration": 3000.0}, - # "active_material": {"GUID": "xxxx", "length_remaining": -1.0}, + # "active_material": {"guid": "xxxx", "length_remaining": -1.0}, # "hotend": {"temperature": {"target": 0.0, "current": 22.8}, "id": "BB 0.4"} # } # ], @@ -421,7 +421,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): temperature = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["temperature"]["current"] self._setHotendTemperature(index, temperature) try: - material_id = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"] + material_id = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] except KeyError: material_id = "" self._setMaterialId(index, material_id) @@ -504,7 +504,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No PrinterCore loaded in slot {0}".format(index + 1))) self._error_message.show() return - if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"] == "": + if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "": Logger.log("e", "No material loaded in slot %s, unable to start print", index + 1) self._error_message = Message( i18n_catalog.i18nc("@info:status", @@ -533,7 +533,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): material = extruder_manager.getExtruderStack(index).findContainer({"type": "material"}) if material: - remote_material_guid = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"] + remote_material_guid = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] if material.getMetaDataEntry("GUID") != remote_material_guid: Logger.log("w", "Extruder %s has a different material (%s) as Cura (%s)", index + 1, remote_material_guid, From 4900a8f38089f382c61ab838e9b0f9ba61732f26 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 26 Oct 2016 14:52:30 +0200 Subject: [PATCH 293/297] Added message to warn user if calibration was not properly done CURA-2731 --- NetworkPrinterOutputDevice.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f674930dc8..f61486e53b 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -545,9 +545,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): remote_material_name = remote_materials[0].getName() warnings.append(i18n_catalog.i18nc("@label", "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}").format(material.getName(), remote_material_name, index + 1)) + try: + is_offset_calibrated = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["offset"]["state"] == "valid" + except KeyError: # Older versions of the API don't expose the offset property, so we must asume that all is well. + is_offset_calibrated = True + + if not is_offset_calibrated: + warnings.append(i18n_catalog.i18nc("@label", "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer.").format(index + 1)) + if warnings: text = i18n_catalog.i18nc("@label", "Are you sure you wish to print with the selected configuration?") - informative_text = i18n_catalog.i18nc("@label", "There is a mismatch between the configuration of the printer and Cura. " + informative_text = i18n_catalog.i18nc("@label", "There is a mismatch between the configuration or calibration of the printer and Cura. " "For the best result, always slice for the PrintCores and materials that are inserted in your printer.") detailed_text = "" for warning in warnings: From b41b98f058d650c923957553414971cf49e62e70 Mon Sep 17 00:00:00 2001 From: Simon Edwards Date: Wed, 26 Oct 2016 16:50:04 +0200 Subject: [PATCH 294/297] OSX would get stuck and act like there was a modal dialog open if we do the callback processing immediately. Delay it slightly. CURA-2801 CLONE - Cura in freeze mode when printing via WiFi --- NetworkPrinterOutputDevice.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index f61486e53b..f70acc0513 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -574,10 +574,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.startPrint() def _configurationMismatchMessageCallback(self, button): - if button == QMessageBox.Yes: - self.startPrint() - else: - Application.getInstance().showPrintMonitor.emit(False) + def delayedCallback(): + if button == QMessageBox.Yes: + self.startPrint() + else: + Application.getInstance().showPrintMonitor.emit(False) + # For some unknown reason Cura on OSX will hang if we do the call back code + # immediately without first returning and leaving QML's event system. + QTimer.singleShot(100, delayedCallback) def isConnected(self): return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error From 688833d36ab23a83a743f69ecaf7cc2f03bfaeef Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 2 Nov 2016 09:31:51 +0100 Subject: [PATCH 295/297] Add better context for 'Print over network' text This should help the translators get it better. --- NetworkPrinterOutputDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 1cf3fd7b9f..c06d6ada81 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -100,7 +100,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setPriority(2) # Make sure the output device gets selected above local file output self.setName(key) - self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print over network")) + self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network")) self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network")) self.setIconName("print") From e7d04f7dc0e24e8060182b591f48d6a9a407a084 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 3 Nov 2016 17:32:17 +0100 Subject: [PATCH 296/297] Fixed some merge issues --- NetworkPrinterOutputDevice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index eef398da57..66ad0d44bf 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -131,7 +131,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._image_request = None self._image_reply = None - self._use_stream = True + self._use_stream = False self._stream_buffer = b"" self._stream_buffer_start_index = -1 @@ -491,11 +491,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.stop() self._camera_timer.stop() - if self._image_reply: + if self._image_reply: self._image_reply.abort() self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress) self._image_reply = None - self._image_request = None + self._image_request = None ## Request the current scene to be sent to a network-connected printer. # From bf670d325a8a0ed7a1106c99ec28e2404f22e6d1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Nov 2016 09:42:05 +0100 Subject: [PATCH 297/297] Camera can now be started & stopped to prevent bandwith issues CURA-2411 --- NetworkPrinterOutputDevice.py | 42 +++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index 66ad0d44bf..356f61b172 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -124,14 +124,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._update_timer.timeout.connect(self._update) self._camera_timer = QTimer() - self._camera_timer.setInterval(2000) # Todo: Add preference for camera update interval + self._camera_timer.setInterval(500) # Todo: Add preference for camera update interval self._camera_timer.setSingleShot(False) - self._camera_timer.timeout.connect(self._update_camera) + self._camera_timer.timeout.connect(self._updateCamera) self._image_request = None self._image_reply = None - self._use_stream = False + self._use_stream = True self._stream_buffer = b"" self._stream_buffer_start_index = -1 @@ -233,14 +233,28 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def ipAddress(self): return self._address - def _start_camera_stream(self): + def _stopCamera(self): + self._camera_timer.stop() + if self._image_reply: + self._image_reply.abort() + self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress) + self._image_reply = None + self._image_request = None + + def _startCamera(self): + if self._use_stream: + self._startCameraStream() + else: + self._camera_timer.start() + + def _startCameraStream(self): ## Request new image url = QUrl("http://" + self._address + ":8080/?action=stream") self._image_request = QNetworkRequest(url) self._image_reply = self._manager.get(self._image_request) self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress) - def _update_camera(self): + def _updateCamera(self): if not self._manager.networkAccessible(): return ## Request new image @@ -489,13 +503,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Stop update timers self._update_timer.stop() - self._camera_timer.stop() - - if self._image_reply: - self._image_reply.abort() - self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress) - self._image_reply = None - self._image_request = None + + self.stopCamera() ## Request the current scene to be sent to a network-connected printer. # @@ -625,7 +634,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self.setConnectionState(ConnectionState.connecting) self._update() # Manually trigger the first update, as we don't want to wait a few secs before it starts. if not self._use_stream: - self._update_camera() + self._updateCamera() Logger.log("d", "Connection with printer %s with ip %s started", self._key, self._address) ## Check if this machine was authenticated before. @@ -633,10 +642,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_key", None) self._update_timer.start() - if self._use_stream: - self._start_camera_stream() - else: - self._camera_timer.start() + #self.startCamera() ## Stop requesting data from printer def disconnect(self): @@ -969,6 +975,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _onStreamDownloadProgress(self, bytes_received, bytes_total): # An MJPG stream is (for our purpose) a stream of concatenated JPG images. # JPG images start with the marker 0xFFD8, and end with 0xFFD9 + if self._image_reply is None: + return self._stream_buffer += self._image_reply.readAll() if self._stream_buffer_start_index == -1: